├── .browserslistrc ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github └── workflows │ ├── deploy.yml │ ├── js-ci.yml │ └── ruby-ci.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .rubocop.yml ├── .ruby-version ├── .vscode ├── extensions.json └── settings.json ├── .yarn ├── releases │ └── yarn-3.1.1.cjs └── sdks │ ├── eslint │ ├── bin │ │ └── eslint.js │ ├── lib │ │ └── api.js │ └── package.json │ ├── integrations.yml │ ├── prettier │ ├── index.js │ └── package.json │ └── typescript │ ├── bin │ ├── tsc │ └── tsserver │ ├── lib │ ├── tsc.js │ ├── tsserver.js │ ├── tsserverlibrary.js │ └── typescript.js │ └── package.json ├── .yarnrc.yml ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Procfile ├── Procfile.dev ├── README.md ├── Rakefile ├── app ├── assets │ ├── builds │ │ └── .keep │ ├── config │ │ └── manifest.js │ ├── fonts │ │ ├── Outfit-Black.woff2 │ │ ├── Outfit-Bold.woff2 │ │ ├── Outfit-ExtraBold.woff2 │ │ ├── Outfit-ExtraLight.woff2 │ │ ├── Outfit-Light.woff2 │ │ ├── Outfit-Medium.woff2 │ │ ├── Outfit-Regular.woff2 │ │ ├── Outfit-SemiBold.woff2 │ │ ├── Outfit-Thin.woff2 │ │ ├── fontawesome │ │ │ ├── LICENSE.txt │ │ │ ├── css │ │ │ │ └── all.min.css │ │ │ └── webfonts │ │ │ │ ├── fa-brands-400.ttf │ │ │ │ ├── fa-brands-400.woff2 │ │ │ │ ├── fa-regular-400.ttf │ │ │ │ ├── fa-regular-400.woff2 │ │ │ │ ├── fa-solid-900.ttf │ │ │ │ ├── fa-solid-900.woff2 │ │ │ │ ├── fa-v4compatibility.ttf │ │ │ │ └── fa-v4compatibility.woff2 │ │ └── fonts.css │ ├── images │ │ ├── icon-128.png │ │ ├── icon-square-128.png │ │ └── icon.svg │ └── stylesheets │ │ └── application.css ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── controllers │ ├── api │ │ ├── application_controller.rb │ │ ├── oauth_controller.rb │ │ ├── reminders_controller.rb │ │ └── users_controller.rb │ ├── application_controller.rb │ ├── auth_controller.rb │ ├── authorizations_controller.rb │ ├── concerns │ │ └── .keep │ ├── errors_controller.rb │ ├── hovercard_controller.rb │ ├── integrations_controller.rb │ ├── landing_controller.rb │ ├── oauth_apps_controller.rb │ ├── oauth_callback_controller.rb │ ├── personal_tokens_controller.rb │ ├── reminders_controller.rb │ ├── sessions_controller.rb │ ├── settings_controller.rb │ └── signup_controller.rb ├── data │ └── oauth.rb ├── helpers │ ├── api │ │ └── oauth_helper.rb │ ├── application_helper.rb │ ├── auth_helper.rb │ ├── authorizations_helper.rb │ ├── errors_helper.rb │ ├── hovercard_helper.rb │ ├── integrations_helper.rb │ ├── landing_helper.rb │ ├── oauth_apps_helper.rb │ ├── oauth_callback_helper.rb │ ├── personal_tokens_helper.rb │ ├── reminders_helper.rb │ ├── settings_helper.rb │ └── signup_helper.rb ├── javascript │ ├── application.ts │ ├── controllers │ │ ├── alert_controller.ts │ │ ├── copy_controller.ts │ │ ├── form_confirm_controller.tsx │ │ ├── form_controller.ts │ │ ├── hovercard_controller.ts │ │ └── index.ts │ ├── shim │ │ └── jsx-shim.ts │ └── types │ │ └── jsx-shim.d.ts ├── jobs │ ├── application_job.rb │ └── cache_link_unfurl_job.rb ├── mailers │ ├── application_mailer.rb │ └── user_mailer.rb ├── models │ ├── api_token.rb │ ├── application_record.rb │ ├── concerns │ │ ├── .keep │ │ └── expirable.rb │ ├── oauth_app.rb │ ├── oauth_grant.rb │ ├── password_reset_request.rb │ ├── reminder.rb │ ├── session.rb │ ├── signup_request.rb │ └── user.rb ├── policies │ ├── oauth_app_policy.rb │ └── reminder_policy.rb ├── services │ ├── github_service.rb │ ├── oauth_service │ │ └── github.rb │ ├── open_graph_service.rb │ └── unfurl_service.rb └── views │ ├── api │ ├── oauth │ │ └── authorize.html.haml │ ├── reminders │ │ ├── _reminder.json.jbuilder │ │ ├── index.json.jbuilder │ │ └── show.json.jbuilder │ └── users │ │ └── me.json.jbuilder │ ├── auth │ ├── forgot_password.html.haml │ ├── forgot_password_submit.html.haml │ ├── forgot_password_verify.html.haml │ └── login.html.haml │ ├── authorizations │ └── _authorization.html.haml │ ├── errors │ ├── internal_server_error.html.haml │ └── not_found.html.haml │ ├── hovercard │ └── unfurl.html.haml │ ├── integrations │ └── index.html.haml │ ├── landing │ ├── _message.html.haml │ ├── _reminder.html.haml │ └── index.html.haml │ ├── layouts │ ├── _sm.html.haml │ ├── application.html.haml │ ├── hovercard.html.haml │ ├── mailer.html.haml │ └── mailer.text.haml │ ├── oauth_apps │ ├── _layout.html.haml │ ├── advanced.html.haml │ ├── edit.html.haml │ ├── index.html.haml │ ├── new.html.haml │ └── show.html.haml │ ├── personal_tokens │ ├── index.html.haml │ └── new.html.haml │ ├── reminders │ ├── _reminder.html.haml │ ├── create.turbo_stream.haml │ ├── destroy.turbo_stream.haml │ └── index.html.haml │ ├── sessions │ └── _session.html.haml │ ├── settings │ ├── _layout.html.haml │ ├── developer.html.haml │ ├── index.html.haml │ └── security.html.haml │ ├── shared │ ├── _alert.html.haml │ ├── _header.html.haml │ ├── _landing_header.html.haml │ ├── _notice.haml │ └── _toast.html.haml │ ├── signup │ ├── index.html.haml │ ├── submit.html.haml │ └── verify.html.haml │ └── user_mailer │ ├── password_changed.html.haml │ ├── password_reset.html.haml │ ├── signup_verification.html.haml │ └── signup_verification.text.haml ├── bin ├── bundle ├── dev ├── esbuild ├── rails ├── rake ├── setup ├── spring └── yarn ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── honeybadger.yml ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── content_security_policy.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── honeybadger.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── permissions_policy.rb │ ├── redis.rb │ ├── sidekiq.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── puma.rb ├── routes.rb ├── routes │ └── api.rb ├── spring.rb └── storage.yml ├── db ├── migrate │ ├── 20211111021027_create_users.rb │ ├── 20211111022726_rename_hashed_password.rb │ ├── 20211111041143_create_sessions.rb │ ├── 20211111052623_create_reminders.rb │ ├── 20211119030654_create_signup_requests.rb │ ├── 20211125033049_unique_fields.rb │ ├── 20211125044511_create_oauth_apps.rb │ ├── 20211125050029_add_name_to_oauth_app.rb │ ├── 20211126050826_add_official_to_oauth_app.rb │ ├── 20211128041643_create_oauth_grants.rb │ ├── 20211128044302_add_expires_at_to_oauth_grant.rb │ ├── 20211128191000_add_fulfilled_to_oauth_grants.rb │ ├── 20211129020838_create_api_tokens.rb │ ├── 20211201171829_add_oauth_app_to_reminders.rb │ ├── 20211201172253_reminder_oauth_app_optional.rb │ ├── 20211202020630_add_ip_and_user_agent_to_sessions.rb │ ├── 20211202174211_add_login_method_to_session.rb │ ├── 20211202225129_add_description_to_api_token.rb │ ├── 20211207152821_add_public_to_oauth_app.rb │ ├── 20211209041857_create_password_reset_requests.rb │ ├── 20211214205049_password_reset_request_fulfilled_default.rb │ ├── 20211215035648_create_active_storage_tables.active_storage.rb │ ├── 20211217022916_add_fields_to_reminder.rb │ ├── 20211217023036_author_name_to_author.rb │ ├── 20211217030224_title_to_description.rb │ ├── 20211219051154_reminder_description_to_text.rb │ ├── 20211229214208_add_installation_url_to_oauth_apps.rb │ ├── 20220122173730_add_service_name_to_active_storage_blobs.active_storage.rb │ ├── 20220122173731_create_active_storage_variant_records.active_storage.rb │ ├── 20220122173732_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb │ ├── 20220123025226_add_source_to_reminder.rb │ ├── 20220224233219_add_discarded_at_to_oauth_apps.rb │ └── 20220320193230_add_last_active_at_to_session.rb ├── schema.rb └── seeds.rb ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── package.json ├── postcss.config.js ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── robots.txt └── site.webmanifest ├── storage └── .keep ├── tailwind.config.js ├── test ├── application_system_test_case.rb ├── channels │ └── application_cable │ │ └── connection_test.rb ├── controllers │ ├── .keep │ ├── auth_controller_test.rb │ ├── authorizations_controller_test.rb │ ├── errors_controller_test.rb │ ├── hovercard_controller_test.rb │ ├── integrations_controller_test.rb │ ├── landing_controller_test.rb │ ├── oauth_apps_controller_test.rb │ ├── oauth_callback_controller_test.rb │ ├── personal_tokens_controller_test.rb │ ├── reminders_controller_test.rb │ ├── settings_controller_test.rb │ └── signup_controller_test.rb ├── fixtures │ ├── api_tokens.yml │ ├── files │ │ └── .keep │ ├── oauth_apps.yml │ ├── oauth_grants.yml │ ├── password_reset_requests.yml │ ├── reminders.yml │ ├── sessions.yml │ ├── signup_requests.yml │ └── users.yml ├── helpers │ └── .keep ├── integration │ └── .keep ├── jobs │ └── cache_link_unfurl_job_test.rb ├── mailers │ ├── .keep │ ├── previews │ │ └── user_mailer_preview.rb │ └── user_mailer_test.rb ├── models │ ├── .keep │ ├── api_token_test.rb │ ├── oauth_app_test.rb │ ├── oauth_grant_test.rb │ ├── password_reset_request_test.rb │ ├── reminder_test.rb │ ├── session_test.rb │ ├── signup_request_test.rb │ └── user_test.rb ├── system │ └── .keep └── test_helper.rb ├── tmp ├── .keep └── pids │ └── .keep ├── tsconfig.json ├── vendor └── .keep └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /*.config.js 2 | /bin/esbuild -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 7 | "parser": "@typescript-eslint/parser", 8 | "parserOptions": { 9 | "ecmaVersion": 13, 10 | "sourceType": "module" 11 | }, 12 | "plugins": ["@typescript-eslint"], 13 | "rules": { 14 | "eqeqeq": "warn" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files. 2 | 3 | # Mark the database schema as having been generated. 4 | db/schema.rb linguist-generated 5 | 6 | # Mark the yarn lockfile as having been generated. 7 | yarn.lock linguist-generated 8 | 9 | # Mark any vendored files as having been vendored. 10 | vendor/* linguist-vendored 11 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | env: 8 | REGISTRY: ghcr.io 9 | IMAGE_NAME: ${{ github.repository }} 10 | 11 | concurrency: deploy 12 | 13 | jobs: 14 | app: 15 | name: App 16 | runs-on: ubuntu-latest 17 | needs: 18 | - docker 19 | steps: 20 | - uses: actions/checkout@v2 21 | - uses: appleboy/ssh-action@v0.1.4 22 | with: 23 | host: "159.223.133.179" 24 | username: dokku 25 | key: ${{ secrets.SSH_PRIVATE_KEY }} 26 | script: git:from-image resolute ghcr.io/resoluteapp/resolute@${{ needs.docker.outputs.digest }} 27 | - name: Report to Honeybadger 28 | uses: honeybadger-io/github-notify-deploy-action@v1 29 | with: 30 | api_key: ${{ secrets.HONEYBADGER_API_KEY }} 31 | - name: Report to Honeybadger (again) 32 | uses: honeybadger-io/github-notify-deploy-action@v1 33 | with: 34 | api_key: ${{ secrets.HONEYBADGER_FRONTEND_API_KEY }} 35 | 36 | docker: 37 | name: Docker 38 | runs-on: ubuntu-latest 39 | permissions: 40 | contents: read 41 | packages: write 42 | outputs: 43 | digest: ${{ steps.push.outputs.digest }} 44 | 45 | steps: 46 | - name: Checkout repository 47 | uses: actions/checkout@v2 48 | 49 | - name: Log in to the Container registry 50 | uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 51 | with: 52 | registry: ${{ env.REGISTRY }} 53 | username: ${{ github.actor }} 54 | password: ${{ secrets.GITHUB_TOKEN }} 55 | 56 | - name: Extract metadata (tags, labels) for Docker 57 | id: meta 58 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 59 | with: 60 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 61 | 62 | - name: Set up Docker Buildx 63 | uses: docker/setup-buildx-action@v1 64 | 65 | - name: Build and push Docker image 66 | id: push 67 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc 68 | with: 69 | context: . 70 | push: true 71 | tags: ${{ steps.meta.outputs.tags }} 72 | labels: ${{ steps.meta.outputs.labels }} 73 | build-args: HONEYBADGER_FRONTEND_API_KEY=${{ secrets.HONEYBADGER_FRONTEND_API_KEY }} 74 | -------------------------------------------------------------------------------- /.github/workflows/js-ci.yml: -------------------------------------------------------------------------------- 1 | name: JavaScript CI 2 | on: 3 | - push 4 | - pull_request 5 | 6 | jobs: 7 | build: 8 | name: Build 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-node@v2 13 | with: 14 | node-version: "16" 15 | - run: yarn install 16 | - run: yarn ts-check 17 | format: 18 | name: Format 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: actions/setup-node@v2 23 | with: 24 | node-version: "16" 25 | - run: yarn install 26 | - run: yarn format:check 27 | lint: 28 | name: Lint 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v2 32 | - uses: actions/setup-node@v2 33 | with: 34 | node-version: "16" 35 | - run: yarn install 36 | - run: yarn lint 37 | -------------------------------------------------------------------------------- /.github/workflows/ruby-ci.yml: -------------------------------------------------------------------------------- 1 | name: Ruby CI 2 | on: 3 | - push 4 | - pull_request 5 | 6 | jobs: 7 | lint: 8 | name: Lint 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: ruby/setup-ruby@v1 13 | with: 14 | bundler-cache: true 15 | - run: bundle exec rubocop app/ config/ 16 | -------------------------------------------------------------------------------- /.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 the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-* 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | /tmp/* 17 | !/log/.keep 18 | !/tmp/.keep 19 | 20 | # Ignore pidfiles, but keep the directory. 21 | /tmp/pids/* 22 | !/tmp/pids/ 23 | !/tmp/pids/.keep 24 | 25 | # Ignore uploaded files in development. 26 | /storage/* 27 | !/storage/.keep 28 | 29 | /public/assets 30 | .byebug_history 31 | 32 | # Ignore master key for decrypting credentials and more. 33 | /config/master.key 34 | 35 | /public/packs 36 | /public/packs-test 37 | /node_modules 38 | /yarn-error.log 39 | yarn-debug.log* 40 | .yarn-integrity 41 | 42 | /config/credentials/development.key 43 | 44 | /app/assets/builds/* 45 | !/app/assets/builds/.keep 46 | 47 | .DS_Store 48 | 49 | # Yarn stuff 50 | .pnp.* 51 | .yarn/* 52 | !.yarn/patches 53 | !.yarn/plugins 54 | !.yarn/releases 55 | !.yarn/sdks 56 | !.yarn/versions -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | app/assets/builds/ 2 | .pnp.* 3 | .yarn/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true 3 | } 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: rubocop-rails 2 | 3 | Style/Documentation: 4 | Enabled: false 5 | Style/GlobalVars: 6 | Enabled: false 7 | Metrics/AbcSize: 8 | Severity: info 9 | Enabled: false 10 | Metrics/CyclomaticComplexity: 11 | Severity: info 12 | Enabled: false 13 | Metrics/PerceivedComplexity: 14 | Severity: info 15 | Enabled: false 16 | Rails/TimeZone: 17 | Enabled: false 18 | Layout/IndentationStyle: 19 | EnforcedStyle: tabs 20 | IndentationWidth: 1 21 | Layout/IndentationWidth: 22 | Width: 1 23 | 24 | Metrics/BlockLength: 25 | Exclude: 26 | - "config/**/*" 27 | 28 | # Some defaults 29 | Gemspec/DateAssignment: # new in 1.10 30 | Enabled: true 31 | Gemspec/RequireMFA: # new in 1.23 32 | Enabled: true 33 | Layout/LineEndStringConcatenationIndentation: # new in 1.18 34 | Enabled: true 35 | Layout/SpaceBeforeBrackets: # new in 1.7 36 | Enabled: true 37 | Lint/AmbiguousAssignment: # new in 1.7 38 | Enabled: true 39 | Lint/AmbiguousOperatorPrecedence: # new in 1.21 40 | Enabled: true 41 | Lint/AmbiguousRange: # new in 1.19 42 | Enabled: true 43 | Lint/DeprecatedConstants: # new in 1.8 44 | Enabled: true 45 | Lint/DuplicateBranch: # new in 1.3 46 | Enabled: true 47 | Lint/DuplicateRegexpCharacterClassElement: # new in 1.1 48 | Enabled: true 49 | Lint/EmptyBlock: # new in 1.1 50 | Enabled: true 51 | Lint/EmptyClass: # new in 1.3 52 | Enabled: true 53 | Lint/EmptyInPattern: # new in 1.16 54 | Enabled: true 55 | Lint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21 56 | Enabled: true 57 | Lint/LambdaWithoutLiteralBlock: # new in 1.8 58 | Enabled: true 59 | Lint/NoReturnInBeginEndBlocks: # new in 1.2 60 | Enabled: true 61 | Lint/NumberedParameterAssignment: # new in 1.9 62 | Enabled: true 63 | Lint/OrAssignmentToConstant: # new in 1.9 64 | Enabled: true 65 | Lint/RedundantDirGlobSort: # new in 1.8 66 | Enabled: true 67 | Lint/RequireRelativeSelfPath: # new in 1.22 68 | Enabled: true 69 | Lint/SymbolConversion: # new in 1.9 70 | Enabled: true 71 | Lint/ToEnumArguments: # new in 1.1 72 | Enabled: true 73 | Lint/TripleQuotes: # new in 1.9 74 | Enabled: true 75 | Lint/UnexpectedBlockArity: # new in 1.5 76 | Enabled: true 77 | Lint/UnmodifiedReduceAccumulator: # new in 1.1 78 | Enabled: true 79 | Lint/UselessRuby2Keywords: # new in 1.23 80 | Enabled: true 81 | Security/IoMethods: # new in 1.22 82 | Enabled: true 83 | Style/ArgumentsForwarding: # new in 1.1 84 | Enabled: true 85 | Style/CollectionCompact: # new in 1.2 86 | Enabled: true 87 | Style/DocumentDynamicEvalDefinition: # new in 1.1 88 | Enabled: true 89 | Style/EndlessMethod: # new in 1.8 90 | Enabled: true 91 | Style/HashConversion: # new in 1.10 92 | Enabled: true 93 | Style/HashExcept: # new in 1.7 94 | Enabled: true 95 | Style/IfWithBooleanLiteralBranches: # new in 1.9 96 | Enabled: true 97 | Style/InPatternThen: # new in 1.16 98 | Enabled: true 99 | Style/MultilineInPatternThen: # new in 1.16 100 | Enabled: true 101 | Style/NegatedIfElseCondition: # new in 1.2 102 | Enabled: true 103 | Style/NilLambda: # new in 1.3 104 | Enabled: true 105 | Style/NumberedParameters: # new in 1.22 106 | Enabled: true 107 | Style/NumberedParametersLimit: # new in 1.22 108 | Enabled: true 109 | Style/OpenStructUse: # new in 1.23 110 | Enabled: true 111 | Style/QuotedSymbols: # new in 1.16 112 | Enabled: true 113 | Style/RedundantArgument: # new in 1.4 114 | Enabled: true 115 | Style/RedundantSelfAssignmentBranch: # new in 1.19 116 | Enabled: true 117 | Style/SelectByRegexp: # new in 1.22 118 | Enabled: true 119 | Style/StringChars: # new in 1.12 120 | Enabled: true 121 | Style/SwapValues: # new in 1.1 122 | Enabled: true 123 | Rails/ActiveRecordCallbacksOrder: # new in 2.7 124 | Enabled: true 125 | Rails/AddColumnIndex: # new in 2.11 126 | Enabled: true 127 | Rails/AfterCommitOverride: # new in 2.8 128 | Enabled: true 129 | Rails/AttributeDefaultBlockValue: # new in 2.9 130 | Enabled: true 131 | Rails/EagerEvaluationLogMessage: # new in 2.11 132 | Enabled: true 133 | Rails/ExpandedDateRange: # new in 2.11 134 | Enabled: true 135 | Rails/FindById: # new in 2.7 136 | Enabled: true 137 | Rails/I18nLocaleAssignment: # new in 2.11 138 | Enabled: true 139 | Rails/Inquiry: # new in 2.7 140 | Enabled: true 141 | Rails/MailerName: # new in 2.7 142 | Enabled: true 143 | Rails/MatchRoute: # new in 2.7 144 | Enabled: true 145 | Rails/NegateInclude: # new in 2.7 146 | Enabled: true 147 | Rails/Pluck: # new in 2.7 148 | Enabled: true 149 | Rails/PluckInWhere: # new in 2.7 150 | Enabled: true 151 | Rails/RedundantTravelBack: # new in 2.12 152 | Enabled: true 153 | Rails/RenderInline: # new in 2.7 154 | Enabled: true 155 | Rails/RenderPlainText: # new in 2.7 156 | Enabled: true 157 | Rails/ShortI18n: # new in 2.7 158 | Enabled: true 159 | Rails/SquishedSQLHeredocs: # new in 2.8 160 | Enabled: true 161 | Rails/TimeZoneAssignment: # new in 2.10 162 | Enabled: true 163 | Rails/UnusedIgnoredColumns: # new in 2.11 164 | Enabled: true 165 | Rails/WhereEquals: # new in 2.9 166 | Enabled: true 167 | Rails/WhereExists: # new in 2.7 168 | Enabled: true 169 | Rails/WhereNot: # new in 2.8 170 | Enabled: true 171 | 172 | Naming/BlockForwarding: # new in 1.24 173 | Enabled: true 174 | Style/FileRead: # new in 1.24 175 | Enabled: true 176 | Style/FileWrite: # new in 1.24 177 | Enabled: true 178 | Style/MapToHash: # new in 1.24 179 | Enabled: true 180 | Rails/CompactBlank: # new in 2.13 181 | Enabled: true 182 | Rails/DurationArithmetic: # new in 2.13 183 | Enabled: true 184 | Rails/RedundantPresenceValidationOnBelongsTo: # new in 2.13 185 | Enabled: true 186 | Rails/RootJoinChain: # new in 2.13 187 | Enabled: true 188 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.0.3 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | // List of extensions which should be recommended for users of this workspace. 5 | "recommendations": [ 6 | "vivaxy.vscode-conventional-commits", 7 | "esbenp.prettier-vscode", 8 | "rebornix.ruby", 9 | "castwide.solargraph", 10 | "karunamurti.haml", 11 | "bradlc.vscode-tailwindcss", 12 | "wingrunr21.vscode-ruby", 13 | "arcanis.vscode-zipfs", 14 | "dbaeumer.vscode-eslint" 15 | ], 16 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 17 | "unwantedRecommendations": [] 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[haml]": { 3 | "editor.detectIndentation": false, 4 | "editor.insertSpaces": false 5 | }, 6 | "[ruby]": { 7 | "editor.detectIndentation": false, 8 | "editor.insertSpaces": false 9 | }, 10 | "scss.lint.unknownAtRules": "ignore", 11 | "css.validate": false, 12 | "search.exclude": { 13 | "**/.yarn": true, 14 | "**/.pnp.*": true 15 | }, 16 | "eslint.nodePath": ".yarn/sdks", 17 | "prettier.prettierPath": ".yarn/sdks/prettier/index.js", 18 | "typescript.tsdk": ".yarn/sdks/typescript/lib", 19 | "typescript.enablePromptUseWorkspaceTsdk": true, 20 | "ruby.format": "rubocop", 21 | "ruby.lint": { 22 | "rubocop": true 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.yarn/sdks/eslint/bin/eslint.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require eslint/bin/eslint.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real eslint/bin/eslint.js your application uses 20 | module.exports = absRequire(`eslint/bin/eslint.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/eslint/lib/api.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require eslint 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real eslint your application uses 20 | module.exports = absRequire(`eslint`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/eslint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint", 3 | "version": "8.6.0-sdk", 4 | "main": "./lib/api.js", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /.yarn/sdks/integrations.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by @yarnpkg/sdks. 2 | # Manual changes might be lost! 3 | 4 | integrations: 5 | - vscode 6 | -------------------------------------------------------------------------------- /.yarn/sdks/prettier/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require prettier/index.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real prettier/index.js your application uses 20 | module.exports = absRequire(`prettier/index.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/prettier/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prettier", 3 | "version": "2.5.1-sdk", 4 | "main": "./index.js", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/bin/tsc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/bin/tsc 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/bin/tsc your application uses 20 | module.exports = absRequire(`typescript/bin/tsc`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/bin/tsserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/bin/tsserver 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/bin/tsserver your application uses 20 | module.exports = absRequire(`typescript/bin/tsserver`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/tsc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/lib/tsc.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/lib/tsc.js your application uses 20 | module.exports = absRequire(`typescript/lib/tsc.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/typescript.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/lib/typescript.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/lib/typescript.js your application uses 20 | module.exports = absRequire(`typescript/lib/typescript.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript", 3 | "version": "4.5.4-sdk", 4 | "main": "./lib/typescript.js", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | yarnPath: .yarn/releases/yarn-3.1.1.cjs 2 | nodeLinker: node-modules 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.0.3 2 | 3 | ARG HONEYBADGER_FRONTEND_API_KEY 4 | 5 | ENV RAILS_ENV production 6 | ENV NODE_ENV production 7 | 8 | WORKDIR /usr/src/app 9 | 10 | RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash - && \ 11 | apt-get install -y nodejs && \ 12 | corepack enable 13 | 14 | COPY . . 15 | 16 | RUN bundle install && \ 17 | SECRET_KEY_BASE=1 bin/rails assets:precompile 18 | 19 | CMD ["bin/rails", "server"] 20 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 5 | 6 | ruby '3.0.3' 7 | 8 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main' 9 | gem 'rails', '~> 7.0', '>= 7.0.1' 10 | # Use Postgres as the database for Active Record 11 | gem 'pg', '~> 1.2', '>= 1.2.3' 12 | # Use Puma as the app server 13 | gem 'puma', '~> 5.0' 14 | 15 | gem 'turbo-rails' 16 | 17 | gem 'faraday' 18 | gem 'faraday_middleware' 19 | 20 | gem 'pundit' 21 | 22 | gem 'haml', '~> 5.2' 23 | 24 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 25 | gem 'jbuilder', '~> 2.7' 26 | 27 | # Use Redis adapter to run Action Cable in production 28 | gem 'redis', '~> 4.0' 29 | gem "redis-namespace", "~> 1.8" 30 | 31 | # Use Active Model has_secure_password 32 | gem 'bcrypt', '~> 3.1.7' 33 | 34 | # Use Active Storage variant 35 | # gem 'image_processing', '~> 1.2' 36 | 37 | # Reduces boot times through caching; required in config/boot.rb 38 | gem 'bootsnap', '~> 1.9', '>= 1.9.3', require: false 39 | 40 | group :development, :test do 41 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 42 | gem 'byebug', platforms: %i[mri mingw x64_mingw] 43 | end 44 | 45 | group :development do 46 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. 47 | gem 'web-console', '>= 4.1.0' 48 | # Display performance information such as SQL time and flame graphs for each request in your browser. 49 | # Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md 50 | gem 'listen', '~> 3.3' 51 | gem 'rack-mini-profiler', '~> 2.0', require: false 52 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 53 | gem 'spring' 54 | 55 | gem 'rubocop', '~> 1.23', require: false 56 | gem 'rubocop-rails', '~> 2.12', require: false 57 | end 58 | 59 | group :test do 60 | # Adds support for Capybara system testing and selenium driver 61 | gem 'capybara', '>= 3.26' 62 | gem 'selenium-webdriver' 63 | # Easy installation and use of web drivers to run system tests with browsers 64 | gem 'webdrivers' 65 | end 66 | 67 | gem "browser", "~> 5.3" 68 | 69 | gem "tzinfo-data", "~> 1.2021" 70 | 71 | gem "jsbundling-rails", "~> 0.2.2" 72 | 73 | gem "cssbundling-rails", "~> 0.2.7" 74 | 75 | gem "sprockets-rails", "~> 3.4" 76 | 77 | gem "nokogiri", "~> 1.13" 78 | 79 | gem "honeybadger", "~> 4.10" 80 | 81 | gem "rinku", "~> 2.0" 82 | 83 | gem "sidekiq", "~> 6.4", require: false 84 | 85 | gem "discard", "~> 1.2" 86 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | release: bin/rails db:migrate 2 | -------------------------------------------------------------------------------- /Procfile.dev: -------------------------------------------------------------------------------- 1 | web: bin/rails server -p 3000 2 | js: yarn build --watch 3 | css: yarn build:css --watch 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 👀 2 | 3 | coming soon... 4 | -------------------------------------------------------------------------------- /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/builds/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/builds/.keep -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../builds 2 | //= link_tree ../images 3 | //= link fonts.css 4 | //= link fontawesome/css/all.min.css 5 | -------------------------------------------------------------------------------- /app/assets/fonts/Outfit-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/fonts/Outfit-Black.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/Outfit-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/fonts/Outfit-Bold.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/Outfit-ExtraBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/fonts/Outfit-ExtraBold.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/Outfit-ExtraLight.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/fonts/Outfit-ExtraLight.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/Outfit-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/fonts/Outfit-Light.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/Outfit-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/fonts/Outfit-Medium.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/Outfit-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/fonts/Outfit-Regular.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/Outfit-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/fonts/Outfit-SemiBold.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/Outfit-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/fonts/Outfit-Thin.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/fontawesome/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/fonts/fontawesome/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /app/assets/fonts/fontawesome/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/fonts/fontawesome/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/fontawesome/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/fonts/fontawesome/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /app/assets/fonts/fontawesome/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/fonts/fontawesome/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/fontawesome/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/fonts/fontawesome/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /app/assets/fonts/fontawesome/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/fonts/fontawesome/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/fontawesome/webfonts/fa-v4compatibility.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/fonts/fontawesome/webfonts/fa-v4compatibility.ttf -------------------------------------------------------------------------------- /app/assets/fonts/fontawesome/webfonts/fa-v4compatibility.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/fonts/fontawesome/webfonts/fa-v4compatibility.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Outfit"; 3 | font-style: normal; 4 | font-weight: 100; 5 | font-display: swap; 6 | src: url("Outfit-Thin.woff2") format("woff"); 7 | } 8 | 9 | @font-face { 10 | font-family: "Outfit"; 11 | font-style: normal; 12 | font-weight: 200; 13 | font-display: swap; 14 | src: url("Outfit-ExtraLight.woff2") format("woff"); 15 | } 16 | 17 | @font-face { 18 | font-family: "Outfit"; 19 | font-style: normal; 20 | font-weight: 300; 21 | font-display: swap; 22 | src: url("Outfit-Light.woff2") format("woff"); 23 | } 24 | 25 | @font-face { 26 | font-family: "Outfit"; 27 | font-style: normal; 28 | font-weight: 400; 29 | font-display: swap; 30 | src: url("Outfit-Regular.woff2") format("woff"); 31 | } 32 | 33 | @font-face { 34 | font-family: "Outfit"; 35 | font-style: normal; 36 | font-weight: 500; 37 | font-display: swap; 38 | src: url("Outfit-Medium.woff2") format("woff"); 39 | } 40 | 41 | @font-face { 42 | font-family: "Outfit"; 43 | font-style: normal; 44 | font-weight: 600; 45 | font-display: swap; 46 | src: url("Outfit-SemiBold.woff2") format("woff"); 47 | } 48 | 49 | @font-face { 50 | font-family: "Outfit"; 51 | font-style: normal; 52 | font-weight: 700; 53 | font-display: swap; 54 | src: url("Outfit-Bold.woff2") format("woff"); 55 | } 56 | 57 | @font-face { 58 | font-family: "Outfit"; 59 | font-style: normal; 60 | font-weight: 800; 61 | font-display: swap; 62 | src: url("Outfit-ExtraBold.woff2") format("woff"); 63 | } 64 | 65 | @font-face { 66 | font-family: "Outfit"; 67 | font-style: normal; 68 | font-weight: 900; 69 | font-display: swap; 70 | src: url("Outfit-Black.woff2") format("woff"); 71 | } 72 | -------------------------------------------------------------------------------- /app/assets/images/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/images/icon-128.png -------------------------------------------------------------------------------- /app/assets/images/icon-square-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/assets/images/icon-square-128.png -------------------------------------------------------------------------------- /app/assets/images/icon.svg: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | body, 7 | html { 8 | @apply bg-black text-white; 9 | } 10 | } 11 | 12 | @layer components { 13 | .btn { 14 | @apply cursor-pointer disabled:cursor-default text-white rounded-md py-2 px-4 shadow-md hover:shadow-lg hover:no-underline disabled:opacity-50 bg-green-600 transition; 15 | } 16 | 17 | a, 18 | .link { 19 | @apply text-green-500 hover:underline cursor-pointer; 20 | } 21 | 22 | .input { 23 | @apply bg-black block w-full py-3 px-4 rounded-md placeholder-gray-500 border-2 border-gray-700 focus:outline-none focus:border-green-500 disabled:cursor-not-allowed disabled:text-gray-500; 24 | 25 | &.unobtrusive { 26 | @apply border-transparent focus:border-green-500; 27 | } 28 | } 29 | 30 | .modal:not(.modal--visible) { 31 | @apply opacity-0; 32 | 33 | & .modal__content { 34 | @apply scale-95; 35 | } 36 | } 37 | 38 | .alert { 39 | @apply text-white rounded-md py-3 px-4 flex justify-between items-center gap-3 shadow-lg bg-red-500; 40 | } 41 | 42 | .notice { 43 | @apply text-white rounded-md py-3 px-4 flex justify-between items-center gap-3 shadow-lg bg-yellow-600; 44 | } 45 | } 46 | 47 | .turbo-progress-bar { 48 | @apply bg-green-700; 49 | } 50 | 51 | [x-cloak] { 52 | display: none !important; 53 | } 54 | 55 | .has-overlay-link a:not(.overlay-link), 56 | .has-overlay-link button { 57 | @apply z-10 relative; 58 | } 59 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationCable 4 | class Channel < ActionCable::Channel::Base 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationCable 4 | class Connection < ActionCable::Connection::Base 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/controllers/api/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Api 4 | class ApplicationController < ActionController::Base 5 | before_action :authenticate 6 | skip_before_action :verify_authenticity_token 7 | 8 | rescue_from ActionController::ParameterMissing do |e| 9 | render json: { 10 | error: 'invalid_input', 11 | description: "Missing required parameter: #{e.param}" 12 | }, status: :bad_request 13 | end 14 | rescue_from ActiveRecord::RecordInvalid do |e| 15 | render json: { 16 | error: 'invalid_input', 17 | description: e 18 | }, status: :bad_request 19 | end 20 | 21 | private 22 | 23 | def authenticate 24 | @token = authenticate_with_http_token do |t, _options| 25 | ApiToken.find_by(token: t) 26 | end 27 | 28 | return render json: { error: 'invalid_token' }, status: :unauthorized if @token.nil? 29 | end 30 | 31 | def require_scope(scope) 32 | # Allow tokens that have the scope, or are personal tokens 33 | return if @token.scope&.include?(scope) || @token.personal? 34 | 35 | render json: { error: 'missing_scope', description: "Missing scope: #{scope}" }, 36 | status: :forbidden 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /app/controllers/api/oauth_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Api 4 | class OauthController < ::ApplicationController 5 | before_action :require_auth, only: %i[authorize authorize_submit] 6 | skip_before_action :verify_authenticity_token, only: [:token] 7 | 8 | def authorize 9 | @hide_header = true 10 | 11 | @app = OauthApp.kept.find_by(client_id: params[:client_id]) 12 | 13 | @scopes = (params[:scope] || '').split(/(?:,\s*|\s)/).map(&:downcase).filter do |scope| 14 | !Oauth.scopes[scope].nil? 15 | end 16 | 17 | @state = params[:state] 18 | @response_type = params[:response_type] || 'code' 19 | 20 | @invalid_response_type = true unless %w[token code].include? @response_type 21 | end 22 | 23 | # rubocop:disable Metrics/MethodLength 24 | def authorize_submit 25 | @app = OauthApp.kept.find_by!(client_id: params[:client_id]) 26 | 27 | uri = URI(@app.redirect_uri) 28 | 29 | case params[:commit] 30 | when 'Cancel' 31 | case params[:response_type] 32 | when 'code' 33 | uri.query = URI.encode_www_form(error: 'access_denied') 34 | when 'token' 35 | uri.fragment = URI.encode_www_form(error: 'access_denied') 36 | end 37 | when 'Connect' 38 | case params[:response_type] 39 | when 'code' 40 | grant = OauthGrant.create!(oauth_app: @app, user: @current_user, scope: JSON.parse(params[:scope])) 41 | uri.query = URI.encode_www_form({ code: grant.code, state: params[:state] }.compact) 42 | when 'token' 43 | scope = JSON.parse(params[:scope]) 44 | 45 | token = ApiToken.create(scope: scope, oauth_app: @app, user: @current_user) 46 | 47 | uri.fragment = URI.encode_www_form({ 48 | access_token: token.token, 49 | token_type: 'bearer', 50 | scope: scope.join(', '), 51 | state: params[:state] 52 | }.compact) 53 | end 54 | end 55 | 56 | redirect_to uri.to_s, allow_other_host: true 57 | end 58 | # rubocop:enable Metrics/MethodLength 59 | 60 | def token 61 | grant_type, code, client_id, client_secret = params.require(%i[grant_type code client_id client_secret]) 62 | 63 | return render_oauth_error 'unsupported_grant_type' if grant_type != 'authorization_code' 64 | 65 | return render_oauth_error 'invalid_client' unless authenticate_oauth_app(client_id, client_secret) 66 | 67 | return render_oauth_error 'invalid_grant' unless check_grant_validity(code) 68 | 69 | issue_access_token 70 | rescue ActionController::ParameterMissing 71 | render_oauth_error 'invalid_request' 72 | end 73 | 74 | private 75 | 76 | def render_oauth_error(error) 77 | render json: { error: error }, status: :bad_request 78 | end 79 | 80 | # Ensures that 81 | # - the client ID belongs to an app 82 | # - the client secret is valid 83 | def authenticate_oauth_app(client_id, client_secret) 84 | @app = OauthApp.kept.find_by(client_id: client_id) 85 | 86 | !(@app.nil? || @app.client_secret != client_secret) 87 | end 88 | 89 | # Ensures that 90 | # - the OAuth grant exists 91 | # - the grant was created by the current app 92 | def check_grant_validity(code) 93 | @grant = OauthGrant.find_by(code: code) 94 | 95 | !(@grant.nil? || @grant.fulfilled || @grant.expired? || @grant.oauth_app != @app) 96 | end 97 | 98 | # Issues an access token 99 | def issue_access_token 100 | token = ApiToken.create(scope: @grant.scope, oauth_app: @app, user: @grant.user) 101 | @grant.fulfill! 102 | 103 | render json: { 104 | access_token: token.token, 105 | token_type: 'bearer', 106 | scope: @grant.scope.join(', ') 107 | } 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /app/controllers/api/reminders_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Api 4 | class RemindersController < Api::ApplicationController 5 | before_action(only: :index) { require_scope('reminders:view') } 6 | before_action(only: :create) { require_scope('reminders:create') } 7 | before_action(only: :destroy) { require_scope('reminders:create') } 8 | 9 | def index 10 | @reminders = @token.user.reminders 11 | end 12 | 13 | def create 14 | @reminder = Reminder.create!(params.permit(:text, :title, :author, :author_avatar, :url, :source) 15 | .merge({ user: @token.user, oauth_app: @token.oauth_app })) 16 | 17 | render 'show', status: :created 18 | end 19 | 20 | def destroy 21 | reminder = Reminder.find_by!(id: params[:id], user: @token.user) 22 | 23 | reminder.destroy 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/controllers/api/users_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Api 4 | class UsersController < Api::ApplicationController 5 | def me 6 | @user = @token.user 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationController < ActionController::Base 4 | include Pundit 5 | 6 | before_action :authenticate 7 | before_action :touch_current_session 8 | 9 | before_action do 10 | Honeybadger.context( 11 | user_id: @current_user&.id, 12 | user_email: @current_user&.email 13 | ) 14 | end 15 | 16 | rescue_from Pundit::NotAuthorizedError do 17 | render 'errors/not_found', status: :not_found 18 | end 19 | 20 | attr_reader :current_user 21 | 22 | private 23 | 24 | # Fetches the logged-in user 25 | def authenticate 26 | token = session[:token] 27 | @current_session = Session.find_by(token: token) 28 | 29 | @current_user = @current_session.user unless @current_session.nil? 30 | end 31 | 32 | def touch_current_session 33 | @current_session&.touch! 34 | end 35 | 36 | # Redirects to login if not logged in 37 | def require_auth 38 | return if @current_user 39 | 40 | flash.notice = 'You need to log in first!' 41 | 42 | redirect_to "/login?redirect_to=#{URI.encode_www_form_component(request.fullpath)}" 43 | end 44 | 45 | # Redirects to home if signed in 46 | def redirect_if_signed_in 47 | redirect_to '/home' if @current_user 48 | end 49 | 50 | def log_in(options) 51 | user_session = Session.create!(user: options[:user], ip: request.ip, user_agent: request.headers['User-Agent'], 52 | login_method: options[:method]) 53 | 54 | session[:token] = user_session.token 55 | 56 | begin 57 | redirect_to(options[:redirect_to] || '/home') 58 | rescue ActionController::Redirecting::UnsafeRedirectError 59 | redirect_to '/' 60 | end 61 | end 62 | 63 | def log_out 64 | Session.destroy_by(token: session[:token]) 65 | session.delete :token 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /app/controllers/auth_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AuthController < ApplicationController 4 | before_action :redirect_if_signed_in, except: [:logout] 5 | 6 | def login 7 | if flash[:prefill_email] 8 | @prefill_email = flash[:prefill_email] 9 | @focus_password = true 10 | end 11 | 12 | @redirect_to = params[:redirect_to] 13 | end 14 | 15 | def auth 16 | user = User.find_by(email: params['email']) 17 | 18 | @prefill_email = params['email'] 19 | 20 | return fail_auth 'User not found.' if user.nil? 21 | 22 | unless user.authenticate(params['password']) 23 | @focus_password = true 24 | 25 | return fail_auth 'Incorrect password.' 26 | end 27 | 28 | log_in user: user, redirect_to: params[:redirect_to] 29 | end 30 | 31 | def logout 32 | log_out 33 | 34 | flash.notice = "You've been logged out!" 35 | 36 | redirect_to '/' 37 | end 38 | 39 | def forgot_password_submit 40 | user = User.find_by(email: params[:email]) 41 | 42 | if user.nil? 43 | flash.now[:alert] = 'User not found.' 44 | render 'forgot_password', status: :unprocessable_entity 45 | else 46 | request = PasswordResetRequest.create(user: user) 47 | 48 | UserMailer.with(email: user.email, code: request.code).password_reset.deliver_later 49 | 50 | @email = user.email 51 | 52 | # Hack to get Turbo Drive to work properly 53 | render status: :unprocessable_entity 54 | end 55 | end 56 | 57 | def forgot_password_verify 58 | request = PasswordResetRequest.find_by(code: params[:code], fulfilled: false) 59 | 60 | return redirect_to '/login', alert: 'Invalid reset link.' if request.nil? 61 | return redirect_to '/login', alert: 'Reset link expired.' if request.expired? 62 | 63 | @code = request.code 64 | @email = request.user.email 65 | end 66 | 67 | def forgot_password_finalize 68 | request = PasswordResetRequest.find_by(code: params[:code], fulfilled: false) 69 | 70 | return redirect_to '/login', alert: 'Invalid reset link.' if request.nil? 71 | return redirect_to '/login', alert: 'Reset link expired.' if request.expired? 72 | 73 | request.fulfill! 74 | 75 | request.user.update!(password: params[:password]) 76 | 77 | UserMailer.with(email: request.user.email).password_changed.deliver_later 78 | 79 | flash.notice = 'Your password has been reset.' 80 | 81 | log_in user: request.user 82 | end 83 | 84 | private 85 | 86 | def fail_auth(message) 87 | flash.now[:alert] = message 88 | 89 | @redirect_to = params[:redirect_to] 90 | 91 | render 'login', status: :unprocessable_entity 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /app/controllers/authorizations_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AuthorizationsController < ApplicationController 4 | before_action :require_auth 5 | 6 | def destroy 7 | ApiToken.destroy_by(user: @current_user, oauth_app_id: params[:id]) 8 | 9 | redirect_to '/settings/security' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/errors_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ErrorsController < ApplicationController 4 | def not_found 5 | render status: :not_found 6 | end 7 | 8 | def internal_server_error 9 | render status: :internal_server_error 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/controllers/hovercard_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class HovercardController < ApplicationController 4 | def unfurl 5 | @url = params[:url] 6 | 7 | metadata = UnfurlService.run(@url) 8 | 9 | return head :not_found if metadata.blank? 10 | 11 | @title = metadata.title 12 | @description = metadata.description 13 | @favicon = metadata.favicon 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/controllers/integrations_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class IntegrationsController < ApplicationController 4 | before_action :require_auth 5 | before_action do 6 | @header_selected = :integrations 7 | end 8 | 9 | def index 10 | @apps = OauthApp.published 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/controllers/landing_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class LandingController < ApplicationController 4 | before_action :redirect_if_signed_in 5 | end 6 | -------------------------------------------------------------------------------- /app/controllers/oauth_apps_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class OauthAppsController < ApplicationController 4 | before_action :require_auth 5 | before_action :find_app 6 | before_action do 7 | @header_selected = :settings 8 | end 9 | 10 | def index 11 | @apps = @current_user.oauth_apps.kept 12 | end 13 | 14 | def show 15 | @authorization_count = ApiToken.where(oauth_app: @app).count('DISTINCT user_id') 16 | end 17 | 18 | def create 19 | app = OauthApp.create!(params.require(:oauth_app).permit(:name).merge(user: current_user)) 20 | 21 | redirect_to app 22 | end 23 | 24 | def update 25 | @app.update(params.require(:oauth_app).permit(:name, :redirect_uri, :icon, :installation_url)) 26 | 27 | flash.notice = 'App has been updated.' 28 | 29 | redirect_back fallback_location: edit_oauth_app_path(@app) 30 | end 31 | 32 | def destroy 33 | @app.discard! 34 | 35 | redirect_to oauth_apps_path, notice: "App \"#{@app.name}\" has been deleted." 36 | end 37 | 38 | private 39 | 40 | def find_app 41 | return unless params[:id] 42 | 43 | @app = OauthApp.kept.find(params[:id]) 44 | 45 | authorize @app, :show? 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /app/controllers/oauth_callback_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class OauthCallbackController < ApplicationController 4 | def github 5 | state = decode_state(params[:state]) if params[:state].present? 6 | access_token = OauthService::Github.new.exchange_code(params[:code]) 7 | 8 | return redirect_to '/' if access_token.nil? 9 | 10 | email = GithubService.new(access_token).primary_email 11 | user = User.find_by(email: email) 12 | 13 | return redirect_to_signup email if user.nil? 14 | 15 | log_in user: user, redirect_to: state&.[]('r'), method: 'GitHub' 16 | end 17 | 18 | private 19 | 20 | def redirect_to_signup(email) 21 | flash.notice = 'Sign up to continue.' 22 | flash[:email] = email 23 | 24 | redirect_to '/signup' 25 | end 26 | 27 | def decode_state(state) 28 | JSON.parse(Base64.urlsafe_decode64(state)) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/controllers/personal_tokens_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class PersonalTokensController < ApplicationController 4 | before_action :require_auth 5 | before_action do 6 | @header_selected = :settings 7 | end 8 | 9 | def index 10 | @tokens = @current_user.api_tokens.personal.order(created_at: :desc) 11 | end 12 | 13 | def create 14 | token = ApiToken.create!(params.permit(:description).merge(user: current_user)) 15 | 16 | flash[:personal_token] = token 17 | 18 | redirect_to personal_tokens_path 19 | end 20 | 21 | def destroy 22 | token = ApiToken.find_by(id: params[:id], user: @current_user, oauth_app: nil) 23 | 24 | token.destroy 25 | 26 | redirect_to personal_tokens_path 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/controllers/reminders_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RemindersController < ApplicationController 4 | before_action :require_auth 5 | before_action do 6 | @header_selected = :reminders 7 | end 8 | 9 | def index 10 | @reminders = @current_user.reminders.order(created_at: :desc) 11 | end 12 | 13 | def create 14 | text = params[:text].strip 15 | 16 | @reminder = Reminder.create!(user: @current_user, text: text) 17 | 18 | respond_to do |format| 19 | format.html { redirect_to '/home' } 20 | format.turbo_stream 21 | end 22 | end 23 | 24 | def destroy 25 | @reminder = Reminder.find(params[:id]) 26 | 27 | authorize @reminder 28 | @reminder.destroy 29 | 30 | respond_to do |format| 31 | format.html { redirect_to '/home' } 32 | format.turbo_stream 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SessionsController < ApplicationController 4 | before_action :require_auth 5 | 6 | def destroy 7 | session = Session.find_by!(id: params[:id], user: @current_user) 8 | 9 | session.destroy 10 | 11 | if session == @current_session 12 | flash.notice = "You've been logged out!" 13 | 14 | redirect_to '/' 15 | else 16 | redirect_to '/settings/security' 17 | end 18 | end 19 | 20 | def destroy_all 21 | @current_user.sessions.where.not(id: @current_session.id).destroy_all 22 | 23 | redirect_to '/settings/security' 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/controllers/settings_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SettingsController < ApplicationController 4 | before_action :require_auth 5 | before_action do 6 | @header_selected = :settings 7 | end 8 | 9 | def update 10 | if current_user.authenticate(params[:current_password]) 11 | current_user.update!(password: params[:new_password]) 12 | 13 | UserMailer.with(email: current_user.email).password_changed.deliver_later 14 | flash.notice = 'Your password has been changed.' 15 | else 16 | flash.alert = 'Incorrect password.' 17 | 18 | flash[:change_password_open] = true 19 | end 20 | 21 | redirect_back fallback_location: '/settings' 22 | end 23 | 24 | def developer 25 | @app_count = @current_user.oauth_apps.kept.count 26 | @personal_token_count = ApiToken.where(oauth_app: nil, user: @current_user).count 27 | end 28 | 29 | def security 30 | @sessions = @current_user.sessions.order(Arel.sql('COALESCE(last_active_at, created_at) DESC')) 31 | @authorizations = ApiToken.select('oauth_app_id, MAX(created_at) AS last_authorized_at') 32 | .not_personal 33 | .where(user: @current_user) 34 | .group(:oauth_app_id) 35 | .order(last_authorized_at: :desc) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/controllers/signup_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class SignupController < ApplicationController 4 | before_action :redirect_if_signed_in 5 | 6 | def index 7 | @prefill_email = flash[:email] unless flash[:email].nil? 8 | end 9 | 10 | def submit 11 | @email = params[:email] 12 | 13 | unless User.find_by(email: @email).nil? 14 | flash.now[:alert] = 'An account with that email address already exists. Maybe you meant to log in?' 15 | @prefill_email = @email 16 | return render 'index', status: :unprocessable_entity 17 | end 18 | 19 | request = SignupRequest.create!(email: @email) 20 | 21 | UserMailer.with(email: @email, verification_code: request.code).signup_verification.deliver_later 22 | 23 | # Hack to get Turbo Drive to work properly 24 | render status: :unprocessable_entity 25 | end 26 | 27 | def verify 28 | request = SignupRequest.find_by(code: params[:code], fulfilled: false) 29 | 30 | return redirect_to '/signup', alert: 'Invalid verification link.' if request.nil? || request.user_signed_up? 31 | return redirect_to '/signup', alert: 'Verification link expired.' if request.expired? 32 | 33 | @email = request.email 34 | @verification_code = request.code 35 | end 36 | 37 | def finalize 38 | request = SignupRequest.find_by(code: params[:verification_code], fulfilled: false) 39 | 40 | return redirect_to '/signup', alert: 'Invalid verification link.' if request.nil? || request.user_signed_up? 41 | return redirect_to '/signup', alert: 'Verification link expired.' if request.expired? 42 | 43 | request.fulfill! 44 | 45 | user = User.create(email: request.email, password: params[:password]) 46 | log_in user: user 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /app/data/oauth.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Oauth 4 | def self.scopes 5 | { 6 | 'reminders:create' => 'Create and delete reminders', 7 | 'reminders:view' => 'View your reminders', 8 | 'user:email' => 'View your email address' 9 | } 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/helpers/api/oauth_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Api 4 | module OauthHelper 5 | def scope_description(name) 6 | Oauth.scopes[name] 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationHelper 4 | def gravatar(email, size = 50) 5 | gravatar_id = Digest::MD5.hexdigest(email) 6 | gravatar_url = "https://www.gravatar.com/avatar/#{gravatar_id}?d=mp&s=#{size}" 7 | image_tag(gravatar_url, class: 'rounded-full', title: email, alt: email) 8 | end 9 | 10 | # rubocop:disable Metrics/MethodLength 11 | def sidebar_link(options) 12 | link_to(options[:title], options[:href], 13 | class: [ 14 | 'px-4', 15 | 'py-2', 16 | 'border-gray-600', 17 | 'border', 18 | 'border-b-0', 19 | 'last:border-b', 20 | 'first:rounded-t-md', 21 | 'last:rounded-b-md', 22 | 'cursor-pointer', 23 | 'hover:bg-gray-700', 24 | *('text-white' unless options[:selected]) 25 | ].join(' ')) 26 | end 27 | # rubocop:enable Metrics/MethodLength 28 | end 29 | -------------------------------------------------------------------------------- /app/helpers/auth_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module AuthHelper 4 | def github_authorization_url(redirect_to) 5 | OauthService::Github.new.authorization_url(redirect_to) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/helpers/authorizations_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module AuthorizationsHelper 4 | end 5 | -------------------------------------------------------------------------------- /app/helpers/errors_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ErrorsHelper 4 | end 5 | -------------------------------------------------------------------------------- /app/helpers/hovercard_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module HovercardHelper 4 | end 5 | -------------------------------------------------------------------------------- /app/helpers/integrations_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module IntegrationsHelper 4 | end 5 | -------------------------------------------------------------------------------- /app/helpers/landing_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module LandingHelper 4 | end 5 | -------------------------------------------------------------------------------- /app/helpers/oauth_apps_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OauthAppsHelper 4 | end 5 | -------------------------------------------------------------------------------- /app/helpers/oauth_callback_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module OauthCallbackHelper 4 | end 5 | -------------------------------------------------------------------------------- /app/helpers/personal_tokens_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module PersonalTokensHelper 4 | end 5 | -------------------------------------------------------------------------------- /app/helpers/reminders_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RemindersHelper 4 | end 5 | -------------------------------------------------------------------------------- /app/helpers/settings_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SettingsHelper 4 | # rubocop:disable Metrics/MethodLength 5 | def browser_to_icon(browser) 6 | if browser.chrome? 7 | 'chrome' 8 | elsif browser.safari? 9 | 'safari' 10 | elsif browser.firefox? 11 | 'firefox-browser' 12 | elsif browser.edge? 13 | 'edge' 14 | else 15 | 'window-maximize' 16 | end 17 | end 18 | # rubocop:enable Metrics/MethodLength 19 | end 20 | -------------------------------------------------------------------------------- /app/helpers/signup_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module SignupHelper 4 | end 5 | -------------------------------------------------------------------------------- /app/javascript/application.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | Stimulus: Application; 4 | User: { 5 | user_id?: number; 6 | user_email?: string; 7 | }; 8 | } 9 | 10 | // Variables provided by bin/esbuild 11 | const HONEYBADGER_API_KEY: string | undefined; 12 | const NODE_ENV: string | undefined; 13 | const GIT_REVISION: string | undefined; 14 | } 15 | 16 | import "@hotwired/turbo-rails"; 17 | import { Application } from "@hotwired/stimulus"; 18 | import controllers from "./controllers"; 19 | 20 | import Honeybadger from "@honeybadger-io/js/dist/browser/honeybadger"; 21 | 22 | Honeybadger.configure({ 23 | apiKey: HONEYBADGER_API_KEY, 24 | environment: NODE_ENV ?? "development", 25 | revision: GIT_REVISION, 26 | }); 27 | 28 | Honeybadger.setContext(window.User); 29 | 30 | import Alpine from "alpinejs"; 31 | import persist from "@alpinejs/persist"; 32 | import collapse from "@alpinejs/collapse"; 33 | 34 | const application = Application.start(); 35 | window.Stimulus = application; 36 | 37 | application.handleError = (error, message, detail) => { 38 | console.warn(message, detail); 39 | Honeybadger.notify(error); 40 | }; 41 | 42 | Alpine.plugin(persist); 43 | Alpine.plugin(collapse); 44 | Alpine.start(); 45 | 46 | // Init Stimulus controllers 47 | controllers(application); 48 | -------------------------------------------------------------------------------- /app/javascript/controllers/alert_controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus"; 2 | 3 | export default class extends Controller { 4 | dismiss() { 5 | this.element.remove(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/javascript/controllers/copy_controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus"; 2 | 3 | export default class extends Controller { 4 | static values = { 5 | text: String, 6 | }; 7 | static targets = ["button"]; 8 | 9 | timeout: number | undefined; 10 | textValue: string; 11 | buttonTarget: HTMLElement; 12 | 13 | disconnect() { 14 | if (this.timeout !== undefined) { 15 | clearTimeout(this.timeout); 16 | this.timeout = undefined; 17 | } 18 | } 19 | 20 | copy() { 21 | navigator.clipboard.writeText(this.textValue); 22 | 23 | this.buttonTarget.innerText = "Copied!"; 24 | 25 | if (this.timeout === undefined) { 26 | this.timeout = window.setTimeout(() => { 27 | this.buttonTarget.innerText = "Copy"; 28 | this.timeout = undefined; 29 | }, 2000); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/javascript/controllers/form_confirm_controller.tsx: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus"; 2 | 3 | export default class extends Controller { 4 | static values = { 5 | title: String, 6 | text: String, 7 | actionText: { type: String, default: "Delete" }, 8 | }; 9 | 10 | titleValue: string; 11 | textValue: string; 12 | actionTextValue: string; 13 | 14 | modal: Element | null; 15 | submitting: boolean; 16 | 17 | initialize() { 18 | this.modal = null; 19 | this.submitting = false; 20 | } 21 | 22 | submit(e: SubmitEvent) { 23 | if (!this.submitting) { 24 | e.preventDefault(); 25 | 26 | this.showModal(); 27 | } else { 28 | this.submitting = false; 29 | } 30 | } 31 | 32 | private hideModal() { 33 | if (this.modal !== null && this.modal !== undefined) { 34 | this.modal.addEventListener("transitionend", () => { 35 | if (this.modal !== null && this.modal !== undefined) { 36 | this.modal.remove(); 37 | this.modal = null; 38 | } 39 | }); 40 | this.modal.classList.remove("modal--visible"); 41 | } 42 | } 43 | 44 | private showModal() { 45 | if (this.modal !== null) return; 46 | 47 | const modal: Element = ( 48 |
response_type
.
19 | - else
20 | .flex.align-center.gap-5.items-center.mb-6
21 | - if @app.icon.attached?
22 | = image_tag @app.icon, class: "h-12 w-auto rounded-md shadow-lg"
23 | %h2.text-2xl.font-normal.font-heading Connect #{@app.name} to your account?
24 |
25 | - if @scopes.length > 0
26 | .border-gray-600.border-2.mb-6.p-5.rounded-md
27 | .mb-2.font-bold #{@app.name} will be able to:
28 |
29 | %ul.fa-ul{style: "--fa-li-margin: 2rem"}
30 | - @scopes.map {|scope| scope_description(scope)}.each do |scope|
31 | %li
32 | %span.fa-li
33 | %i.fas.fa-square-check.text-green-400
34 | = scope
35 |
36 |
37 | - if @app.official
38 | .text-gray-400.text-sm
39 | %i.fa-solid.fa-fw.fa-shield.text-green-500
40 | %strong= @app.name
41 | is a trusted integration created by Resolute.
42 | - else
43 | .text-gray-400.text-sm
44 | %i.fa-solid.fa-fw.fa-info-circle
45 | %strong= @app.name
46 | hasn't been verified by Resolute.
47 |
48 | = form_with data: { turbo: false } do |form|
49 | = form.hidden_field :client_id, value: @app.client_id
50 | = form.hidden_field :scope, value: @scopes.to_json
51 | = form.hidden_field :state, value: @state unless @state.blank?
52 | = form.hidden_field :response_type, value: @response_type
53 | .flex.gap-3.mt-6
54 | = form.submit "Connect", class: "btn"
55 | = form.submit "Cancel", class: "btn bg-gray-700"
56 |
57 | - if @app.nil? || @app.redirect_uri.blank?
58 | .w-96.text-left.text-gray-500
59 | %a.text-gray-500(href="/home") ← Home
60 |
--------------------------------------------------------------------------------
/app/views/api/reminders/_reminder.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.id reminder.id
4 | json.text reminder.text
5 | json.title reminder.title
6 | json.url reminder.url
7 | json.author reminder.author
8 | json.author_avatar reminder.author_avatar
9 | json.created_at reminder.created_at
10 |
--------------------------------------------------------------------------------
/app/views/api/reminders/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.ignore_nil!
4 |
5 | json.array! @reminders, partial: 'api/reminders/reminder', as: :reminder
6 |
--------------------------------------------------------------------------------
/app/views/api/reminders/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.ignore_nil!
4 |
5 | json.partial! 'api/reminders/reminder', reminder: @reminder
6 |
--------------------------------------------------------------------------------
/app/views/api/users/me.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.id @user.id
4 |
5 | if @token.scope&.include?('user:email') || @token.oauth_app.nil?
6 | json.email @user.email
7 | else
8 | json.email nil
9 | end
10 |
--------------------------------------------------------------------------------
/app/views/auth/forgot_password.html.haml:
--------------------------------------------------------------------------------
1 | - content_for :page_title, "Reset your password"
2 |
3 | = render "layouts/sm", title: "Reset your password", back_href: "/login", back_text: "Log in" do
4 | = form_with do |form|
5 | = form.email_field :email, placeholder: "Email", class: "input mb-6", required: true, autofocus: true
6 |
7 | .flex.justify-between.items-center
8 | = form.submit "Continue", class: "btn"
9 |
--------------------------------------------------------------------------------
/app/views/auth/forgot_password_submit.html.haml:
--------------------------------------------------------------------------------
1 | - content_for :page_title, "Reset your password"
2 |
3 | = render "layouts/sm", title: "Reset your password", back_href: "/forgot-password", back_text: "Back" do
4 | Check your email
5 | %span.text-gray-400 (#{@email})
6 | for a verification link. 🔗
7 |
--------------------------------------------------------------------------------
/app/views/auth/forgot_password_verify.html.haml:
--------------------------------------------------------------------------------
1 | - content_for :page_title, "Reset your password"
2 |
3 | = render "layouts/sm", title: "Reset your password" do
4 | = form_with do |form|
5 | = form.hidden_field :code, value: @code
6 | = form.email_field :email, disabled: true, placeholder: "Email", class: "input mb-4", required: true, value: @email
7 | = form.password_field :password, placeholder: "New password", class: "input mb-6", required: true, autofocus: true
8 |
9 | .flex.justify-between.items-center
10 | = form.submit "Reset", class: "btn"
11 |
--------------------------------------------------------------------------------
/app/views/auth/login.html.haml:
--------------------------------------------------------------------------------
1 | - content_for :page_title, "Log in"
2 |
3 | = render "layouts/sm", title: "Log in", back_href: "/", back_text: "Home" do
4 | = form_with do |form|
5 | = form.hidden_field(:redirect_to, value: @redirect_to) unless @redirect_to.blank?
6 | = form.email_field :email, placeholder: "Email", class: "input mb-4", autofocus: !@focus_password, required: true, value: @prefill_email
7 | = form.password_field :password, placeholder: "Password", class: "input mb-6", autofocus: @focus_password, required: true
8 | .flex.justify-between.items-center
9 | = form.submit "Log in", class: "btn"
10 | %a.text-sm(href="/forgot-password") Forgot password?
11 |
12 | %h3.text-gray-500.mt-10.mb-4.text-center Continue with
13 |
14 | .flex.gap-3
15 | %a.flex-1{href: github_authorization_url(@redirect_to)}
16 | %button.w-full.btn.bg-black GitHub
17 |
--------------------------------------------------------------------------------
/app/views/authorizations/_authorization.html.haml:
--------------------------------------------------------------------------------
1 | .bg-black.p-5.rounded-md.flex.justify-between.items-center.gap-1
2 | .flex.flex-col.gap-1
3 | .flex.gap-2.items-center
4 | - if authorization.oauth_app.icon.attached?
5 | = image_tag authorization.oauth_app.icon, class: "h-5 w-auto rounded-md"
6 |
7 | = authorization.oauth_app.name
8 | .text-sm.text-gray-400 Last authorized #{time_ago_in_words authorization.last_authorized_at} ago
9 | = form_with url: authorization_path(authorization.oauth_app.id), method: :delete, data: { controller: "form-confirm", action: "form-confirm#submit", "form-confirm-title-value" => "Revoke #{authorization.oauth_app.name}'s access to your account?", "form-confirm-text-value" => "This can't be undone.", "form-confirm-action-text-value" => "Revoke" } do
10 | %button.btn.bg-gray-700.shrink-0.hover:bg-red-500.transition-colors(type="submit") Revoke
11 |
--------------------------------------------------------------------------------
/app/views/errors/internal_server_error.html.haml:
--------------------------------------------------------------------------------
1 | .p-10.mt-20.text-center.border-t-4.bg-gray-800.border-red-500.rounded-md.w-fit.mx-auto
2 | %h1.text-8xl.font-mono.text-gray-300 500
3 | %h3.text-4xl.text-gray-300.font-light.mb-5 Something unexpected happened.
4 |
5 | %p.text-gray-300 We track these errors automatically and are working to resolve this.
6 |
--------------------------------------------------------------------------------
/app/views/errors/not_found.html.haml:
--------------------------------------------------------------------------------
1 | .text-center.pt-40
2 | %h1.text-8xl.font-mono.text-gray-400 404
3 | %h3.text-4xl.text-gray-400.font-light Page not found
--------------------------------------------------------------------------------
/app/views/hovercard/unfurl.html.haml:
--------------------------------------------------------------------------------
1 | .flex.items-center.gap-3.mb-3
2 | .bg-white.rounded-md.p-1.shrink-0
3 | %img.h-5.w-auto{src: @favicon}
4 | %a.text-xl.text-bold.text-green-500.whitespace-nowrap.overflow-hidden.text-ellipsis{href: @url, target: "_blank"}= @title
5 | %p.mb-2.flex.align-stretch.before:bg-gray-600.before:block.before:mr-3.before:rounded-full.before:shrink-0.cursor-default{class: "before:content-[''] before:w-1"}= @description
6 | %a.text-sm.text-gray-400.text-ellipsis.whitespace-nowrap.overflow-hidden{href: @url, target: "_blank"}= @url
7 |
--------------------------------------------------------------------------------
/app/views/integrations/index.html.haml:
--------------------------------------------------------------------------------
1 | .w-full.max-w-4xl.mx-auto
2 | .text-4xl.font-heading.mb-10.bg-green-600.rounded-md.text-center.py-20 Resolute works with the apps you already use.
3 |
4 | .grid.grid-cols-2.gap-5
5 | - @apps.each do |app|
6 | %a.text-white.p-5.bg-gray-800.rounded-md.flex.items-center.justify-between.transition-transform.hover:no-underline{href: app.installation_url, class: "hover:-translate-y-0.5", target: "_blank"}
7 | .flex.gap-4.items-center
8 | - if app.icon.attached?
9 | .relative
10 | = image_tag app.icon, class: "h-10 w-auto rounded-md"
11 | - if app.official
12 | .absolute.-bottom-2.-right-2.z-50.shadow-md.rounded-md.text-sm{class: "py-0.5 px-1 bg-gray-700/75", title: "This is a trusted integration created by Resolute."}
13 | %i.fa-solid.fa-shield.fa-fw
14 |
15 | .font-heading.font-heading.font-semibold.text-2xl
16 | = app.name
17 | - if @current_user.api_tokens.where(oauth_app: app).count > 0
18 | %button.btn.bg-transparent.ring-1.ring-green-600.ring-inset.text-green-500 Installed
19 | - else
20 | %button.btn Install
21 |
--------------------------------------------------------------------------------
/app/views/landing/_message.html.haml:
--------------------------------------------------------------------------------
1 | - if selected
2 | .flex.gap-3.items-start.relative.rounded-md
3 | %img.h-11.rounded-md(src="https://cloud-5txrrpkms-hack-club-bot.vercel.app/0avataaars.png")
4 | .flex.flex-col.gap-1
5 | .flex.gap-2.items-center(class="-mt-1")
6 | .font-bold.text-sm= user
7 | .text-gray-500.text-xs= Time.now.strftime "%l:%M %p"
8 | %div= text
9 | .absolute.top-0.right-1{"class" => "-mt-5", "x-data" => "{ open: false }"}
10 | .absolute{class: "-top-1 -left-1"}
11 | .w-3.h-3.bg-blue-500.absolute.top-0.left-0.rounded-full
12 | .w-3.h-3.bg-blue-500.absolute.top-0.left-0.rounded-full.animate-ping
13 | .bg-gray-800.px-2.rounded-md.border.border-gray-700.cursor-pointer.hover:bg-gray-700{"class" => "py-1.5", "@click" => "open = true"}
14 | %i.fa-solid.fa-ellipsis-v.fa-fw
15 | %ul.absolute.top-full.right-0.bg-gray-800.rounded-md.p-2.w-52.mt-2.border.border-gray-700.z-10.shadow-lg{"x-show" => "open", "x-cloak" => true, "@click.outside" => "open = false"}
16 | %li.py-2.px-3.hover:bg-gray-700.rounded-md.cursor-not-allowed.text-gray-500 Mark unread
17 | %li.py-2.px-3.hover:bg-gray-700.rounded-md.cursor-not-allowed.text-gray-500 Copy link
18 | %li.py-2.px-3.hover:bg-gray-700.rounded-md.cursor-pointer.flex.items-center.gap-2{"@click" => "reminders.push({text: \"#{text}\", id: Math.random()}); open = false"}
19 | = image_tag "icon-square-128.png", size: "25x25", alt: "Resolute logo", class: "rounded-md"
20 | %span Add to Resolute
21 | - else
22 | .flex.gap-3.items-start.relative.rounded-md.group{"x-data" => "{ open: false }"}
23 | %img.h-11.rounded-md.group-hover:opacity-100.transition-opacity{"src" => "https://cloud-5txrrpkms-hack-club-bot.vercel.app/0avataaars.png", ":class" => "open ? 'opacity-100' : 'opacity-50'"}
24 | .flex.flex-col.gap-1.group-hover:opacity-100.transition-opacity{":class" => "open ? 'opacity-100' : 'opacity-50'"}
25 | .flex.gap-2.items-center(class="-mt-1")
26 | .font-bold.text-sm= user
27 | .text-gray-500.text-xs= Time.now.strftime "%l:%M %p"
28 | %div= text
29 | .absolute.top-0.right-1.group-hover:opacity-100.transition-opacity{"class" => "-mt-5", ":class" => "open ? 'opacity-100' : 'opacity-0'", "x-cloak" => true}
30 | .bg-gray-800.px-2.rounded-md.border.border-gray-700.cursor-pointer.hover:bg-gray-700{"class" => "py-1.5", "@click" => "open = true"}
31 | %i.fa-solid.fa-ellipsis-v.fa-fw
32 | %ul.absolute.top-full.right-0.bg-gray-800.rounded-md.p-2.w-52.mt-2.border.border-gray-700.z-10.shadow-lg{"x-show" => "open", "x-cloak" => true, "@click.outside" => "open = false"}
33 | %li.py-2.px-3.hover:bg-gray-700.rounded-md.cursor-not-allowed.text-gray-500 Mark unread
34 | %li.py-2.px-3.hover:bg-gray-700.rounded-md.cursor-not-allowed.text-gray-500 Copy link
35 | %li.py-2.px-3.hover:bg-gray-700.rounded-md.cursor-pointer.flex.items-center.gap-2{"@click" => "reminders.push({text: \"#{text}\", id: Math.random()}); open = false"}
36 | = image_tag "icon-square-128.png", size: "25x25", alt: "Resolute logo", class: "rounded-md"
37 | %span Add to Resolute
38 |
--------------------------------------------------------------------------------
/app/views/landing/_reminder.html.haml:
--------------------------------------------------------------------------------
1 | .group.bg-black.p-5.rounded-md.flex.justify-between.items-center.gap-5.border-2.border-transparent.relative.break-words.cursor-default
2 | .flex.flex-col.gap-3.min-w-0.grow
3 | .flex.gap-3.items-center.text-sm.text-gray-300
4 | %img.w-5.rounded-full{src: author_avatar}
5 | %span= author
6 |
7 | .break-words.min-w-0.text-white.hover:no-underline{"x-text" => "reminder.text"}
8 |
9 | .text-xs.text-gray-400 from #{app_name}
10 |
11 | %button.text-gray-500.hover:text-red-500.cursor-pointer.opacity-100.sm:opacity-0.group-hover:opacity-100.transition{"type" => "submit", "title" => "Delete", "@click" => "reminders = reminders.filter(r => r.id != reminder.id)"}
12 | %i.fa-solid.fa-fw.fa-trash.text-xl
13 |
--------------------------------------------------------------------------------
/app/views/landing/index.html.haml:
--------------------------------------------------------------------------------
1 | = render "shared/toast"
2 |
3 | .bg-gray-800.flex.items-center.justify-center.px-10.py-28.sm:py-36.lg:py-40
4 | %h1.lg:text-6xl.text-5xl.font-heading.max-w-xl.text-center
5 | Forgetting to reply is a thing of the past.
6 |
7 | .px-10.mt-10
8 | .mx-auto.w-full.max-w-5xl
9 | .text-3xl.lg:text-4xl.font-heading.mb-10.text-center.border-2.border-gray-800.py-5.px-5.rounded-md
10 | .mb-2 Hey, look! You've just received a message.
11 | .text-xl.lg:text-2xl Can't reply right now? Add it to Resolute.
12 | .flex-col.md:flex-row.flex.gap-4{"x-data" => "{ reminders: $persist([]) }"}
13 | .flex-col.grow.basis-0
14 | .bg-gray-800.rounded-t-md.px-2.py-1.text-sm.flex.items-center.gap-1
15 | .h-3.w-3.bg-red-400.rounded-full
16 | .h-3.w-3.bg-yellow-400.rounded-full
17 | .h-3.w-3.bg-green-400.rounded-full
18 | .ml-3 Messaging App 9000
19 | .border-2.border-gray-800.p-7.rounded-b-md.flex.flex-col.gap-4
20 | = render "message", selected: false, text: "🏴☠️ Ahoy, matey!", user: "buccaneer123"
21 | .w-full.bg-red-500.rounded-full.h-px
22 | = render "message", selected: true, text: "Mind helpin' me with a little computer trouble? The printer not be printin' me treasure map ☠️", user: "buccaneer123"
23 | .flex-col.grow.basis-0
24 | .bg-gray-800.rounded-t-md.px-2.py-1.text-sm.flex.items-center.gap-1
25 | .h-3.w-3.bg-red-400.rounded-full
26 | .h-3.w-3.bg-yellow-400.rounded-full
27 | .h-3.w-3.bg-green-400.rounded-full
28 | .ml-3 Resolute
29 | .p-7.bg-gray-800.rounded-b-md.flex.flex-col.gap-5
30 | %template{"x-if" => "reminders.length == 0"}
31 | .text-gray-500.text-center.mb-4.mt-1 Nothing here, yet...
32 | %template{"x-for" => "reminder in reminders"}
33 | = render "reminder", author: "buccaneer123", author_avatar: "https://cloud-5txrrpkms-hack-club-bot.vercel.app/0avataaars.png", app_name: "Messaging App 9000"
34 |
35 | .px-10.py-12.mt-10.bg-gray-800
36 | .w-full.max-w-4xl.mx-auto.flex.justify-between.items-center.flex-col.md:flex-row.gap-5
37 | %h1.font-heading.font-bold.text-3xl.xs:text-4xl.text-center.md:text-left
38 | Available now for Slack.
39 | %br/
40 | .text-gray-500.text-2xl.xs:text-3xl More apps coming soon!
41 | %a.btn.text-lg.xs:text-xl.py-3.px-6{href: "/signup"} Sign up
42 |
43 | .px-10.py-5.text-gray-500.text-center
44 | .border-r.border-r-gray-700.inline-block.pr-5.mr-3
45 | %a.text-inherit{href: "https://status.useresolute.com", target: "_blank"} Status
46 | ·
47 | %a.text-inherit{href: "https://api.useresolute.com", target: "_blank"} API
48 | %a.hover:no-underline{href: "https://twitter.com/useresolute", target: "_blank"}
49 | %i.fab.fa-twitter.fa-fw
50 | %a.hover:no-underline{href: "https://github.com/resoluteapp", target: "_blank"}
51 | %i.fab.fa-github.fa-fw
--------------------------------------------------------------------------------
/app/views/layouts/_sm.html.haml:
--------------------------------------------------------------------------------
1 | .px-5
2 | .flex.flex-col.gap-5.items-stretch.justify-center.w-full.max-w-sm.mx-auto.mt-20.mb-10
3 | .h-auto.bg-gray-800.border-t-4.rounded-md.p-7.text-white{class: alert.present? ? "border-red-500" : "border-green-400"}
4 | = render "shared/alert"
5 | = render "shared/notice"
6 |
7 | %h2.text-2xl.mb-6.ml-1.font-heading= title
8 |
9 | = yield
10 |
11 | - if local_assigns[:back_href].present?
12 | .text-left.text-gray-500
13 | %a.text-gray-500{href: back_href} ← #{local_assigns.fetch :back_text, "Back"}
14 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.haml:
--------------------------------------------------------------------------------
1 | !!! 5
2 | %html(lang="en")
3 | %head
4 | %title= content_for(:page_title) || "Resolute"
5 | %meta(name="viewport" content="width=device-width,initial-scale=1")/
6 | %meta(name="turbo-cache-control" content="no-preview")/
7 | = csrf_meta_tags
8 | = csp_meta_tag
9 |
10 | %link(rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png")
11 | %link(rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png")
12 | %link(rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png")
13 | %link(rel="manifest" href="/site.webmanifest")
14 |
15 | :javascript
16 | window.User = { user_id: #{@current_user&.id.to_json.html_safe}, user_email: #{@current_user&.email.to_json.html_safe} }
17 |
18 | = stylesheet_link_tag 'fonts', media: 'all'
19 | = stylesheet_link_tag 'fontawesome/css/all.min.css', media: 'all'
20 | = stylesheet_link_tag 'application', media: 'all'
21 | = javascript_include_tag 'application', defer: true
22 |
23 | = javascript_include_tag 'https://analytics.useresolute.com/umami.js',
24 | async: true,
25 | defer: true,
26 | "data-website-id" => "be12769c-47ed-4f1b-8c03-f20cc06e4ec2",
27 | "data-domains" => "useresolute.com" if Rails.env.production?
28 |
29 | %body
30 | - unless @hide_header
31 | - if @current_user.nil?
32 | = render "shared/landing_header"
33 | - else
34 | = render "shared/header", selected: @header_selected
35 |
36 | = yield
37 |
--------------------------------------------------------------------------------
/app/views/layouts/hovercard.html.haml:
--------------------------------------------------------------------------------
1 | .bg-gray-800.rounded-md.p-5.border.border-gray-700.max-w-md.shadow-xl= yield
2 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.html.haml:
--------------------------------------------------------------------------------
1 | !!! 5
2 | %html
3 | %head
4 | %meta(http-equiv="Content-Type" content="text/html; charset=utf-8")/
5 | :css
6 | body {
7 | font-family: "sans-serif";
8 | }
9 |
10 | %body= yield
--------------------------------------------------------------------------------
/app/views/layouts/mailer.text.haml:
--------------------------------------------------------------------------------
1 | = yield
2 |
--------------------------------------------------------------------------------
/app/views/oauth_apps/_layout.html.haml:
--------------------------------------------------------------------------------
1 | .w-full.max-w-4xl.block.mx-auto.bg-gray-800.p-7.rounded-md
2 | .flex.justify-between.items-center.mb-10
3 | %h1.text-xl.font-heading
4 | %span.text-gray-500
5 | %a.text-gray-500(href="/developer") Settings
6 | \/
7 | %a.text-gray-500(href="/developer/apps") Apps
8 | \/
9 | = @app.name
10 | -# - if @app.public
11 | -# %a.btn{href: integration_path(@app), target: "_blank"} Show public app page
12 |
13 | .flex.gap-7
14 | .w-64
15 | %ul.flex.flex-col
16 | = sidebar_link(title: "OAuth", href: oauth_app_path(@app), selected: selected == "credentials")
17 | = sidebar_link(title: "Settings", href: edit_oauth_app_path(@app), selected: selected == "settings")
18 | = sidebar_link(title: "Advanced", href: advanced_oauth_app_path(@app), selected: selected == "advanced")
19 | .grow
20 | = render "shared/alert"
21 | = render "shared/notice"
22 |
23 | = yield
--------------------------------------------------------------------------------
/app/views/oauth_apps/advanced.html.haml:
--------------------------------------------------------------------------------
1 | = render "layout", selected: "advanced" do
2 | .border-2.border-red-500.rounded-md.p-5.flex.justify-between.items-center
3 | .flex.flex-col
4 | .text-lg Delete this app
5 | .text-sm.text-gray-400 This can't be undone.
6 | = form_with url: oauth_app_path(@app), method: :delete, data: { controller: "form-confirm", action: "form-confirm#submit", "form-confirm-title-value" => "Delete this app?", "form-confirm-text-value" => "This will permanently uninstall #{@app.name} for all users, and prevent them from installing it in the future." } do |form|
7 | %button.btn.bg-red-500(type="submit") Delete app
8 |
--------------------------------------------------------------------------------
/app/views/oauth_apps/edit.html.haml:
--------------------------------------------------------------------------------
1 | = render "layout", selected: "settings" do
2 | = form_with model: @app do |form|
3 | = form.label :name, "App name", class: "block mb-2 text-sm text-gray-400"
4 | = form.text_field :name, class: "input mb-4", required: true, placeholder: "My Cool App", autofocus: true
5 | - if @app.public
6 | %label.block.mb-2.text-sm.text-gray-400(for="oauth_app_installation_url") Installation URL
7 | = form.text_field :installation_url, class: "input mb-4", placeholder: "https://my-cool-app.com/install"
8 | = form.label :icon, "Icon", class: "block mb-2 text-sm text-gray-400"
9 | = form.file_field :icon, accept: "image/*", class: "block mb-4 text-gray-400 file:cursor-pointer file:text-white file:rounded-md file:py-2 file:px-4 file:shadow-md file:bg-green-600 file:border-0 file:mr-4"
10 | - if @app.icon.attached?
11 | %div
12 | .relative.group.inline-block
13 | -# %button.absolute.top-1.right-1.py-1.rounded-md.cursor-pointer.transition-opacity.opacity-0.group-hover:opacity-100(class="bg-black/40 px-1.5")
14 | -# %i.fa-solid.fa-times.fa-fw
15 | = image_tag @app.icon, class: "h-40 w-auto mb-6 rounded-lg"
16 |
17 | = form.submit "Save", class: "btn"
18 |
--------------------------------------------------------------------------------
/app/views/oauth_apps/index.html.haml:
--------------------------------------------------------------------------------
1 | .w-full.max-w-4xl.block.mx-auto.bg-gray-800.p-7.rounded-md
2 | .flex.justify-between.items-start.mb-10
3 | %h1.text-xl.font-heading
4 | %span.text-gray-500
5 | %a.text-gray-500(href="/developer") Settings
6 | \/
7 | Apps
8 | %a.btn{href: new_oauth_app_path} New
9 |
10 | = render "shared/alert"
11 | = render "shared/notice"
12 |
13 | - if @apps.length == 0
14 | %h2.text-center.mb-10.mt-15.text-gray-500 Nothing here, yet...
15 | - else
16 | .grid.grid-cols-2.gap-5
17 | - @apps.each do |app|
18 | %a.text-white.text-xl.p-5.bg-black.font-heading.font-semibold.rounded-md.text-white.transition-transform.hover:no-underline{class: "hover:-translate-y-0.5", href: oauth_app_path(app)}
19 | - if app.public
20 | %i.fa-solid.fa-globe.text-green-500.inline-block.mr-1(title="This app is listed publicly.")
21 | - if app.official
22 | %i.fa-solid.fa-shield.text-green-500.inline-block.mr-1(title="Wahoo! This is an officially developed integration.")
23 | = app.name
--------------------------------------------------------------------------------
/app/views/oauth_apps/new.html.haml:
--------------------------------------------------------------------------------
1 | .w-full.max-w-4xl.block.mx-auto.bg-gray-800.p-7.rounded-md
2 | %h1.text-xl.mb-10.font-heading
3 | %span.text-gray-500
4 | %a.text-gray-500(href="/developer") Settings
5 | \/
6 | %a.text-gray-500(href="/developer/apps") Apps
7 | \/
8 | New
9 |
10 | = form_with model: OauthApp.new, class: "block mx-auto" do |form|
11 | = form.text_field :name, class: "input mb-4", placeholder: "App name", autofocus: true, required: true
12 | = form.submit "Create", class: "btn"
--------------------------------------------------------------------------------
/app/views/oauth_apps/show.html.haml:
--------------------------------------------------------------------------------
1 | = render "layout", selected: "credentials" do
2 | .mb-5
3 | .text-sm.text-gray-400 Client ID
4 | .flex.items-center.gap-3{"data-controller" => "copy", "data-copy-text-value" => @app.client_id}
5 | .text-lg.font-mono.select-all= @app.client_id
6 | %button.btn.text-sm.bg-gray-700(data-action="copy#copy" data-copy-target="button") Copy
7 |
8 | .mb-5
9 | .text-sm.text-gray-400 Client Secret
10 | .flex.items-center.gap-3{"data-controller" => "copy", "data-copy-text-value" => @app.client_secret}
11 | .text-lg.font-mono.select-all= @app.client_secret
12 | %button.btn.text-sm.bg-gray-700(data-action="copy#copy" data-copy-target="button") Copy
13 |
14 | = form_with model: @app, class: "mb-8" do |form|
15 | = form.label :redirect_uri, "Redirect URI", class: "block mb-2 text-sm text-gray-400"
16 | .flex.gap-3.items-center
17 | = form.url_field :redirect_uri, class: "input", placeholder: "https://my-cool-app.com/oauth/callback", autofocus: true
18 |
19 | = form.submit "Save", class: "btn"
20 |
21 | .text-sm.text-gray-400.mb-1 Installed by #{pluralize(@authorization_count, 'user')}
22 | .text-sm.text-gray-400 Created #{time_ago_in_words @app.created_at} ago
23 |
--------------------------------------------------------------------------------
/app/views/personal_tokens/index.html.haml:
--------------------------------------------------------------------------------
1 | .w-full.max-w-4xl.block.mx-auto.bg-gray-800.p-7.rounded-md
2 | .flex.justify-between.items-start.mb-10
3 | %h1.text-xl.font-heading
4 | %span.text-gray-500
5 | %a.text-gray-500(href="/developer") Settings
6 | \/
7 | Personal tokens
8 | %a.btn{href: new_personal_token_path} New
9 |
10 | = render "shared/alert"
11 | = render "shared/notice"
12 |
13 | - if flash[:personal_token]
14 | .border.border-green-500.bg-green-800.p-5.rounded-md.mb-10.flex.items-start{"data-controller" => "copy alert", "data-copy-text-value" => flash[:personal_token]["token"]}
15 | .flex-grow.flex.flex-col.items-start
16 | .bg-green-600.rounded-md.py-1.px-2= flash[:personal_token]["description"]
17 | .flex.items-center.gap-3
18 | .font-mono.text-lg.select-all= flash[:personal_token]["token"]
19 | %button.btn.text-sm(data-action="copy#copy" data-copy-target="button") Copy
20 | .opacity-50.mt-2.text-sm Remember to save this token, because you won't see it again.
21 | %button{"data-action" => "alert#dismiss"}
22 | %i.fa-solid.fa-times
23 |
24 | - if @tokens.length == 0
25 | %h2.text-center.mb-10.mt-15.text-gray-500 Nothing here, yet...
26 | - else
27 | .grid.grid-cols-2.gap-5
28 | - @tokens.each do |token|
29 | %li.bg-black.p-5.rounded-md.flex.justify-between.items-center.gap-1
30 | .flex.flex-col.gap-1
31 | = token.description
32 | .text-sm.text-gray-400 Created #{time_ago_in_words token.created_at} ago
33 |
34 | = form_with url: personal_token_path(token), method: :delete, data: { controller: "form-confirm", action: "form-confirm#submit", "form-confirm-title-value" => "Revoke this token?", "form-confirm-text-value" => "Any scripts or applications using this token will stop working.", "form-confirm-action-text-value" => "Revoke" } do
35 | %button.btn.bg-gray-700.shrink-0.hover:bg-red-500.transition-colors(type="submit") Revoke
36 |
--------------------------------------------------------------------------------
/app/views/personal_tokens/new.html.haml:
--------------------------------------------------------------------------------
1 | .w-full.max-w-4xl.block.mx-auto.bg-gray-800.p-7.rounded-md
2 | %h1.text-xl.mb-10.font-heading
3 | %span.text-gray-500
4 | %a.text-gray-500(href="/developer") Settings
5 | \/
6 | %a.text-gray-500{href: personal_tokens_path} Personal tokens
7 | \/
8 | New
9 |
10 | = form_with url: personal_tokens_path, class: "block mx-auto" do |form|
11 | = form.text_field :description, class: "input mb-4", placeholder: "Description", autofocus: true, required: true
12 | = form.submit "Create", class: "btn"
--------------------------------------------------------------------------------
/app/views/reminders/_reminder.html.haml:
--------------------------------------------------------------------------------
1 | %div{id: dom_id(reminder)}
2 | .group.bg-black.p-5.rounded-md.flex.justify-between.items-center.gap-5.border-2.border-transparent.relative.break-words.cursor-default.has-overlay-link{class: reminder.url.present? && "transition hover:-translate-y-0.5 hover:shadow-lg"}
3 | - if reminder.url.present?
4 | %a.absolute.inset-0.z-0.overlay-link{target: "_blank", href: reminder.url}
5 | .flex.flex-col.gap-3.min-w-0.grow
6 | - if reminder.title.present?
7 | %h2.font-semibold.font-heading.break-words.min-w-0.text-xl.text-green-500.group-2-hover:underline{class: "-mb-0.5"}= reminder.title
8 |
9 | - if reminder.author.present?
10 | .flex.gap-3.items-center.text-sm.text-gray-300
11 | - if reminder.author_avatar.present?
12 | %img.w-5.rounded-full{src: reminder.author_avatar}
13 | %span= reminder.author
14 |
15 | - if reminder.text.present?
16 | - if reminder.title.present?
17 | -# Add a li'l left border if there's a title
18 | .break-words.min-w-0.text-white.hover:no-underline.border-l-2.border-gray-500.pl-3= reminder.to_html
19 | - else
20 | .break-words.min-w-0.text-white.hover:no-underline= reminder.to_html
21 |
22 | - if reminder.oauth_app.present? || reminder.source.present?
23 | .text-xs.text-gray-400= [reminder.oauth_app&.pretty_name, reminder.source].compact.join(" · ")
24 |
25 |
26 | = form_with url: reminder_path(reminder), method: :delete, class: "shrink-0" do |form|
27 | %button.text-gray-500.hover:text-red-500.cursor-pointer.opacity-100.sm:opacity-0.group-hover:opacity-100.translate-x-0.group-hover:translate-x-0.transition{type: "submit", title: "Delete", class: reminder.url.blank? ? "sm:translate-x-2" : "sm:translate-x-4"}
28 | %i.fa-solid.fa-fw.fa-trash.text-xl
29 |
--------------------------------------------------------------------------------
/app/views/reminders/create.turbo_stream.haml:
--------------------------------------------------------------------------------
1 | = turbo_stream.prepend "reminders" do
2 | = render @reminder
3 |
--------------------------------------------------------------------------------
/app/views/reminders/destroy.turbo_stream.haml:
--------------------------------------------------------------------------------
1 | = turbo_stream.remove @reminder
--------------------------------------------------------------------------------
/app/views/reminders/index.html.haml:
--------------------------------------------------------------------------------
1 | = render "shared/toast"
2 |
3 | .flex.flex-col.items-stretch.h-screen
4 | = turbo_stream_from @current_user, :reminders
5 |
6 | .px-0.sm:px-10.grow.sm:grow-0
7 | .w-full.max-w-4xl.block.mx-auto.bg-gray-800.p-7.rounded-md.rounded-b-none.sm:rounded-b-md.h-full
8 | = form_with url: "/reminders", data: { controller: "form", action: "turbo:submit-end->form#reset" } do |form|
9 | .flex.flex-col.mb-10.items-stretch.gap-2
10 | .flex.gap-4.items-stretch
11 | %input.input.block(type="text" placeholder="Remind me about..." name="text" autofocus required)
12 | %button.btn.hidden.xs:inline-block(type="submit") Add
13 |
14 | .text-sm.text-gray-400.pl-1
15 | Or,
16 | %a(href="/integrations") find an integration
17 |
18 | -# - if @reminders.length == 0
19 | -# %h2.text-center.mb-10.mt-15.text-gray-500 Nothing here, yet...
20 | -# - else
21 | .grid.grid-cols-1.md:grid-cols-2.gap-5#reminders
22 | - @reminders.each do |reminder|
23 | = render reminder
24 |
--------------------------------------------------------------------------------
/app/views/sessions/_session.html.haml:
--------------------------------------------------------------------------------
1 | .bg-black.p-5.rounded-md.flex.justify-between.items-center.gap-1
2 | .flex.flex-col.gap-1
3 | %div
4 | %i.inline-block.text-gray-500.mr-1.fab{class: ["fa-#{browser_to_icon(session.browser)}", session == @current_session && "text-green-500"]}
5 | %span #{session.browser.name} on #{session.browser.platform.name}
6 | %span.font-mono.text-gray-400.inline-block.ml-2= session.ip
7 | .text-sm.text-gray-400
8 | - if session == @current_session
9 | Active now · current session
10 | - else
11 | Active #{time_ago_in_words(session.last_active_at || session.created_at)} ago
12 |
13 | - unless session.login_method.blank?
14 | · via #{session.login_method}
15 |
16 | = form_with url: session_path(session), method: :delete, class: "shrink-0" do
17 | %button.btn.bg-gray-700.shrink-0.hover:bg-red-500.transition-colors(type="submit") Log out
--------------------------------------------------------------------------------
/app/views/settings/_layout.html.haml:
--------------------------------------------------------------------------------
1 | .w-full.max-w-4xl.block.mx-auto.bg-gray-800.p-7.rounded-md
2 | %h1.text-xl.mb-10.font-heading Settings
3 |
4 | .flex.gap-7
5 | .w-64
6 | %ul.flex.flex-col.mb-5
7 | = sidebar_link(title: "General", href: "/settings", selected: selected == "general")
8 | = sidebar_link(title: "Security", href: "/settings/security", selected: selected == "security")
9 |
10 | %ul.flex.flex-col
11 | = sidebar_link(title: "Developer", href: "/developer", selected: selected == "developer")
12 | .grow
13 | = render "shared/alert"
14 | = render "shared/notice"
15 |
16 | = yield
--------------------------------------------------------------------------------
/app/views/settings/developer.html.haml:
--------------------------------------------------------------------------------
1 | = render "layout", selected: "developer" do
2 | .grid.grid-cols-2.gap-5
3 | %a.bg-blue-600.text-xl.p-5.font-semibold.font-heading.rounded-md.text-white.transition-transform.hover:no-underline{class: "hover:-translate-y-0.5", href: oauth_apps_path}
4 | %i.fa-solid.fa-code.mr-2.fa-fw
5 | Your apps
6 | %span.text-sm.bg-blue-500.font-normal.font-sans.px-2.rounded-md.ml-1(class="py-0.5")= @app_count
7 | %a.bg-green-600.text-xl.p-5.font-semibold.font-heading.rounded-md.text-white.transition-transform.hover:no-underline{class: "hover:-translate-y-0.5", href: personal_tokens_path}
8 | %i.fa-solid.fa-key.mr-2.fa-fw
9 | Personal tokens
10 | %span.text-sm.bg-green-500.font-normal.font-sans.px-2.rounded-md.ml-1(class="py-0.5")= @personal_token_count
11 | %a.bg-yellow-600.text-xl.p-5.font-semibold.font-heading.rounded-md.text-white.transition-transform.hover:no-underline(class="hover:-translate-y-0.5" href="https://api.useresolute.com" data-turbo="false")
12 | %i.fa-solid.fa-book.mr-2.fa-fw
13 | Documentation
14 | %a.bg-rose-500.text-xl.p-5.font-semibold.font-heading.rounded-md.text-white.transition-transform.hover:no-underline(class="hover:-translate-y-0.5" href="https://github.com/resoluteapp/resolute/discussions" target="_blank" data-turbo="false")
15 | %i.fa-solid.fa-users.mr-2.fa-fw
16 | Community
17 |
18 | .text-gray-400.mt-5.text-sm
19 | Psssst... did you know that Resolute is open-source?
20 |
--------------------------------------------------------------------------------
/app/views/settings/index.html.haml:
--------------------------------------------------------------------------------
1 | = render "layout", selected: "general" do
2 | .flex.items-center.gap-5.mb-10
3 | = gravatar @current_user.email, 100
4 | %div
5 | .text-xl= @current_user.email
6 | %em.text-sm Manage your avatar at Gravatar.
7 |
8 | %div{"x-data" => "{ open: #{flash[:change_password_open] ? 'true' : 'false'} }", "x-effect" => "open && setTimeout(() => $refs.currentPassword.focus(), 250)"}
9 | %button.btn.bg-gray-700{"@click" => "open = !open"}
10 | %i.fa-solid.mr-1.fa-fw{":class" => "open ? 'fa-lock-open' : 'fa-lock'"}
11 | Change your password
12 |
13 | %div{"x-show" => "open", "x-collapse" => true, "x-cloak" => true}
14 | = form_with url: "/settings", class: "mt-5" do |form|
15 | = form.password_field :current_password, required: true, class: "input mb-4", placeholder: "Current password", "x-ref" => "currentPassword", autofocus: flash[:change_password_open]
16 | = form.password_field :new_password, required: true, class: "input mb-4", placeholder: "New password"
17 |
18 | .flex.items-center
19 | = form.submit "Save", class: "btn mr-5"
20 | .text-gray-400.text-sm This won't log anyone out; visit the Security page to do that.
21 |
--------------------------------------------------------------------------------
/app/views/settings/security.html.haml:
--------------------------------------------------------------------------------
1 | = render "layout", selected: "security" do
2 | .mb-5.flex.justify-between.items-center
3 | %h2.text-lg.text-gray-400
4 | Sessions
5 | .inline-block.text-sm.bg-gray-700.font-normal.font-sans.px-2.rounded-md(class="py-0.5")= @sessions.length
6 | = form_with url: destroy_all_sessions_path, data: { controller: "form-confirm", action: "form-confirm#submit", "form-confirm-title-value" => "Log out everywhere?", "form-confirm-text-value" => "This will log you out of every device except for your current one.", "form-confirm-action-text-value" => "Log out" } do
7 | %button.btn.bg-red-500{type: "submit", disabled: @sessions.length <= 1} Log out everywhere else
8 |
9 | .flex.flex-col.gap-3.mb-10{"x-data" => "{ unfurled: false }"}
10 | - @sessions[0, 2].each do |session|
11 | = render session
12 | - if @sessions.length > 2
13 | %div.flex.flex-col.gap-3{"x-show" => "unfurled", "x-cloak" => true}
14 | - @sessions[2..].each do |session|
15 | = render session
16 | %div
17 | .inline-block.link{"@click" => "unfurled = !unfurled", "x-text" => "unfurled ? 'See fewer' : 'See all'"} See all
18 |
19 | %h2.text-lg.text-gray-400.mb-5
20 | Authorized apps
21 | - if @authorizations.length > 0
22 | .inline-block.text-sm.bg-gray-700.font-normal.font-sans.px-2.rounded-md(class="py-0.5")= @authorizations.length
23 |
24 | - if @authorizations.length == 0
25 | %h2.text-center.mb-8.text-gray-500 You haven't connected any apps to your account.
26 | - else
27 | .flex.flex-col.gap-3{"x-data" => "{ unfurled: false }"}
28 | - @authorizations[0, 2].each do |authorization|
29 | = render "authorizations/authorization", authorization: authorization
30 | - if @authorizations.length > 2
31 | %div.flex.flex-col.gap-3{"x-show" => "unfurled", "x-cloak" => true}
32 | - @authorizations[2..].each do |authorization|
33 | = render "authorizations/authorization", authorization: authorization
34 | %div
35 | .inline-block.link{"@click" => "unfurled = !unfurled", "x-text" => "unfurled ? 'See fewer' : 'See all'"} See all
36 |
--------------------------------------------------------------------------------
/app/views/shared/_alert.html.haml:
--------------------------------------------------------------------------------
1 | - if alert
2 | %div.alert.mb-6(data-controller="alert")
3 | = alert
4 | %button.bg-transparent(data-action="alert#dismiss")
5 | %i.fa-solid.fa-times
--------------------------------------------------------------------------------
/app/views/shared/_header.html.haml:
--------------------------------------------------------------------------------
1 | .flex.py-5.px-5.sm:px-10.justify-between.bg-black.mb-5.z-40.sticky.top-0
2 | -# Desktop navbar
3 | .hidden.sm:flex.items-center.gap-5
4 | %a.mr-5.shrink-0(href="/home" title="Resolute")
5 | = image_tag "icon.svg", size: "50x50", alt: "Resolute logo"
6 | %a{href: "/home", class: [selected != :reminders && "text-gray-500"]} Reminders
7 | %a{href: "/integrations", class: [selected != :integrations && "text-gray-500"]} Integrations
8 | %a{href: "/settings", class: [selected != :settings && "text-gray-500"]} Settings
9 |
10 |
11 | -# Mobile navbar
12 | .flex.sm:hidden.items-center.gap-5
13 | %a.mr-5.sm:mr-5.shrink-0(href="/home" title="Resolute")
14 | = image_tag "icon.svg", size: "50x50", alt: "Resolute logo"
15 | %a.btn.px-3{href: "/home", class: [selected != :reminders && "bg-gray-700"]}
16 | %i.fa-solid.fa-home.fa-fw
17 | %a.btn.px-3{href: "/integrations", class: [selected != :integrations && "bg-gray-700"]}
18 | %i.fa-solid.fa-plug.fa-fw
19 |
20 | .flex.items-center.gap-5.relative{"x-data" => "{ open: false }"}
21 | .flex.items-center.gap-4.cursor-pointer.px-3{"@click" => "open = !open"}
22 | = gravatar @current_user.email, 40
23 | %i.fa-solid.fa-chevron-down
24 | %ul.absolute.top-full.right-0.bg-gray-800.rounded-md.p-2.w-52.mt-2.border.border-gray-700.z-10.shadow-lg{"x-show" => "open", "x-cloak" => true, "x-transition" => true, "@click.outside" => "open = false"}
25 | %a.hover:no-underline{href: "/settings"}
26 | %li.py-2.px-3.hover:bg-gray-700.rounded-md.text-white.flex.items-center.gap-2
27 | Settings
28 | = form_with url: "/logout", method: :post do |form|
29 | %li.hover:bg-gray-700.rounded-md.text-white.flex.items-center.gap-2
30 | %button.w-full.text-left.py-2.px-3{type: "submit"}
31 | Log out
32 |
--------------------------------------------------------------------------------
/app/views/shared/_landing_header.html.haml:
--------------------------------------------------------------------------------
1 | .flex.py-5.px-5.sm:px-10.justify-between.sticky.top-0.bg-black.z-40.items-center
2 | %a.flex.items-start.gap-1.text-white.hover:no-underline{href: "/"}
3 | .flex.gap-5.items-center
4 | = image_tag "icon.svg", size: "50x50", alt: "Resolute logo"
5 | %h1.font-heading.text-4xl.font-light.hidden.sm:block resolute
6 | .bg-blue-600.rounded-md.text-xs.text-blue-100(class="py-0.5 px-1.5") Beta
7 |
8 | - if @current_user.nil?
9 | .flex.gap-5.items-center
10 | %a.btn.bg-gray-700(href="/login")
11 | %span.hidden.xs:inline-block Log in
12 | %i.fa-solid.fa-sign-in-alt.inline-block.xs:hidden
13 | %a.btn(href="/signup") Sign up
14 | - else
15 | .flex.gap-5.items-center
16 | %a.btn(href="/home") Launch app
17 | = gravatar @current_user.email
18 |
--------------------------------------------------------------------------------
/app/views/shared/_notice.haml:
--------------------------------------------------------------------------------
1 | - if notice
2 | %div.notice.mb-6(data-controller="alert")
3 | = notice
4 | %button.bg-transparent(data-action="alert#dismiss")
5 | %i.fa-solid.fa-times
--------------------------------------------------------------------------------
/app/views/shared/_toast.html.haml:
--------------------------------------------------------------------------------
1 | - if notice
2 | .notice.inline-flex.fixed.top-16.z-50.animate-toast{class: "left-1/2 -translate-x-1/2"}= notice
3 |
4 | - if alert
5 | .alert.inline-flex.fixed.top-16.z-50.animate-toast{class: "left-1/2 -translate-x-1/2"}= alert
6 |
--------------------------------------------------------------------------------
/app/views/signup/index.html.haml:
--------------------------------------------------------------------------------
1 | - content_for :page_title, "Sign up"
2 |
3 | = render "layouts/sm", title: "Sign up", back_href: "/", back_text: "Home" do
4 | = form_with do |form|
5 | = form.email_field :email, placeholder: "Email", class: "input mb-6", autofocus: true, required: true, value: @prefill_email
6 |
7 | .flex.justify-between.items-center
8 | = form.submit "Continue", class: "btn"
9 |
--------------------------------------------------------------------------------
/app/views/signup/submit.html.haml:
--------------------------------------------------------------------------------
1 | - content_for :page_title, "Sign up"
2 |
3 | = render "layouts/sm", title: "Almost there!", back_href: "/signup" do
4 | Check your email
5 | %span.text-gray-400 (#{@email})
6 | for a verification link! 🔗
7 |
--------------------------------------------------------------------------------
/app/views/signup/verify.html.haml:
--------------------------------------------------------------------------------
1 | - content_for :page_title, "Sign up"
2 |
3 | = render "layouts/sm", title: "Sign up" do
4 | = form_with do |form|
5 | = form.hidden_field :verification_code, value: @verification_code
6 | = form.email_field :email, disabled: true, placeholder: "Email", class: "input mb-4", required: true, value: @email
7 | = form.password_field :password, placeholder: "Password", class: "input mb-6", required: true, autofocus: true
8 |
9 | .flex.justify-between.items-center
10 | = form.submit "Sign up", class: "btn"
11 |
--------------------------------------------------------------------------------
/app/views/user_mailer/password_changed.html.haml:
--------------------------------------------------------------------------------
1 | %p Just a heads up, your Resolute account password was just changed.
2 |
3 | %p If this wasn't you, please reset your password immediately.
--------------------------------------------------------------------------------
/app/views/user_mailer/password_reset.html.haml:
--------------------------------------------------------------------------------
1 | %p You (or someone pretending to be you) just requested a password reset. Click here to reset your password.
2 |
3 | %p If this wasn't you, you can safely ignore this email.
--------------------------------------------------------------------------------
/app/views/user_mailer/signup_verification.html.haml:
--------------------------------------------------------------------------------
1 | %p Welcome to Resolute, #{@email}!
2 |
3 | %p
4 | One more step: just click this link to verify your email address.
--------------------------------------------------------------------------------
/app/views/user_mailer/signup_verification.text.haml:
--------------------------------------------------------------------------------
1 | Welcome to Resolute, #{@email}!
2 |
3 | One more step: just go to #{url_for controller: 'signup', action: 'verify', code: @verification_code} to verify your email address.
--------------------------------------------------------------------------------
/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
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 ||=
65 | env_var_version || cli_arg_version ||
66 | lockfile_version
67 | end
68 |
69 | def bundler_requirement
70 | return "#{Gem::Requirement.default}.a" unless bundler_version
71 |
72 | bundler_gem_version = Gem::Version.new(bundler_version)
73 |
74 | requirement = bundler_gem_version.approximate_recommendation
75 |
76 | return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
77 |
78 | requirement += ".a" if bundler_gem_version.prerelease?
79 |
80 | requirement
81 | end
82 |
83 | def load_bundler!
84 | ENV["BUNDLE_GEMFILE"] ||= gemfile
85 |
86 | activate_bundler
87 | end
88 |
89 | def activate_bundler
90 | gem_error = activation_error_handling do
91 | gem "bundler", bundler_requirement
92 | end
93 | return if gem_error.nil?
94 | require_error = activation_error_handling do
95 | require "bundler/version"
96 | end
97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
99 | exit 42
100 | end
101 |
102 | def activation_error_handling
103 | yield
104 | nil
105 | rescue StandardError, LoadError => e
106 | e
107 | end
108 | end
109 |
110 | m.load_bundler!
111 |
112 | if m.invoked_as_script?
113 | load Gem.bin_path("bundler", "bundle")
114 | end
115 |
--------------------------------------------------------------------------------
/bin/dev:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | if ! command -v foreman &> /dev/null
4 | then
5 | echo "Installing foreman..."
6 | gem install foreman
7 | fi
8 |
9 | foreman start -f Procfile.dev
10 |
--------------------------------------------------------------------------------
/bin/esbuild:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const esbuild = require("esbuild");
4 | const { pnpPlugin } = require("@yarnpkg/esbuild-plugin-pnp");
5 |
6 | const util = require("util");
7 | const exec = util.promisify(require("child_process").exec);
8 |
9 | const watch = process.argv.includes("--watch");
10 |
11 | (async () => {
12 | const { stdout: revision } = await exec("git rev-parse HEAD 2> /dev/null");
13 |
14 | await esbuild.build({
15 | entryPoints: ["app/javascript/application.ts"],
16 | bundle: true,
17 | minify: process.env.NODE_ENV === "production",
18 | watch: watch
19 | ? {
20 | onRebuild(error, result) {
21 | if (!error) {
22 | console.log("Rebuilt successfully.");
23 | }
24 | },
25 | }
26 | : undefined,
27 | inject: ["app/javascript/shim/jsx-shim.ts"],
28 | outdir: "app/assets/builds",
29 | jsxFactory: "jsxCreateElement",
30 | // plugins: [pnpPlugin()],
31 | define: {
32 | HONEYBADGER_API_KEY: JSON.stringify(
33 | process.env.HONEYBADGER_FRONTEND_API_KEY
34 | ),
35 | NODE_ENV: JSON.stringify(process.env.NODE_ENV),
36 | GIT_REVISION: JSON.stringify(revision.trim()),
37 | },
38 | });
39 |
40 | if (watch) {
41 | console.log("Watching for changes...\n");
42 | } else {
43 | console.log("Done!");
44 | }
45 | })();
46 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path("../config/application", __dir__)
3 | require_relative "../config/boot"
4 | require "rails/commands"
5 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative "../config/boot"
3 | require "rake"
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/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 set up or update your development environment automatically.
13 | # This script is idempotent, so that you can run it at any time 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 | # puts "\n== Copying sample files =="
21 | # unless File.exist?("config/database.yml")
22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml"
23 | # end
24 |
25 | puts "\n== Preparing database =="
26 | system! "bin/rails db:prepare"
27 |
28 | puts "\n== Removing old logs and tempfiles =="
29 | system! "bin/rails log:clear tmp:clear"
30 |
31 | puts "\n== Restarting application server =="
32 | system! "bin/rails restart"
33 | end
34 |
--------------------------------------------------------------------------------
/bin/spring:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | if !defined?(Spring) && [nil, "development", "test"].include?(ENV["RAILS_ENV"])
3 | gem "bundler"
4 | require "bundler"
5 |
6 | # Load Spring without loading other gems in the Gemfile, for speed.
7 | Bundler.locked_gems&.specs&.find { |spec| spec.name == "spring" }&.tap do |spring|
8 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
9 | gem "spring", spring.version
10 | require "spring/binstub"
11 | rescue Gem::LoadError
12 | # Ignore when Spring is not installed.
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/bin/yarn:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_ROOT = File.expand_path('..', __dir__)
3 | Dir.chdir(APP_ROOT) do
4 | yarn = ENV["PATH"].split(File::PATH_SEPARATOR).
5 | select { |dir| File.expand_path(dir) != __dir__ }.
6 | product(["yarn", "yarn.cmd", "yarn.ps1"]).
7 | map { |dir, file| File.expand_path(file, dir) }.
8 | find { |file| File.executable?(file) }
9 |
10 | if yarn
11 | exec yarn, *ARGV
12 | else
13 | $stderr.puts "Yarn executable was not detected in the system."
14 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
15 | exit 1
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/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 | Rails.application.load_server
7 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'boot'
4 |
5 | require 'rails/all'
6 |
7 | # Require the gems listed in Gemfile, including any gems
8 | # you've limited to :test, :development, or :production.
9 | Bundler.require(*Rails.groups)
10 |
11 | module Resolute
12 | class Application < Rails::Application
13 | # Initialize configuration defaults for originally generated Rails version.
14 | config.load_defaults 7.0
15 |
16 | # Configuration for the application, engines, and railties goes here.
17 | #
18 | # These settings can be overridden in specific environments using the files
19 | # in config/environments, which are processed later.
20 | #
21 | # config.time_zone = "Central Time (US & Canada)"
22 | # config.eager_load_paths << Rails.root.join("extras")
23 |
24 | config.session_store :cookie_store, key: '_resolute_session', expire_after: 30.days
25 | config.exceptions_app = routes
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
4 |
5 | require 'bundler/setup' # Set up gems listed in the Gemfile.
6 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations.
7 |
--------------------------------------------------------------------------------
/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: redis
3 | url: redis://localhost:6379
4 |
5 | test:
6 | adapter: test
7 |
8 | production:
9 | adapter: redis
10 | url: <%= ENV.fetch("REDIS_URL", "redis://localhost:6379") %>
11 | channel_prefix: resolute_production
12 |
--------------------------------------------------------------------------------
/config/credentials.yml.enc:
--------------------------------------------------------------------------------
1 | IJgpTOZ5u3JnGoYZaxVLGMXkY1W1bQXYM3C37Fbd3Wt5QUdpAKQqIL2SBstsbxRiydjvXdt1HC/y1bxTdfaX/bhw8DvOGopKpsLaKlfxuuGQTbUU+un1v3w52hqfPLaoEuP2xDfUTE0aRxY6pdd5WgtpAcwUDKP8iJTvZWewOf5xy7/adZXXDQusl7z3F2JiWDyOqIdS75yZTbS9cujY7LLJ7Llj9x0NLhnJL7NhTh/+RcDSbYCWIWvtC5NfwKgfbDuHcVdD5HY4jpzlvYPF9/uNh7SBTeYfkuN8wGb8R6mYMmjlJpCPicalPUhxJsecWYbaeYJLp7KGMTrEmjtTLK7vsei7zqDYRQM1tZJbcw3zjsYqZFiW3R6pvPdtbFKlwKrXKYXV+ypcrNtaVxYoSbJjsPID43BXKHNjS8e2RT3eRPSM3LTyDYDMYbblnYFBaiuyJ4veU81Li6av+kPSbhIH+jo1E0t3AH0saJkSsUidTA85q1to46WuRph9JL/hAtLOadQtbzJ6J52weXenVSLEkQiTS1zCwNcXnJ+cTvc9Xj+Lz4jPqBPivq2ZSHTl6LJIUQTRP8MsPXdyYMO8SUr5VwOQkXgYoSj4jl0NhhiPjtiyUj+gT70sqCQmVVpdyXIlUceAEHhTBUSEXfV+xsBx3lknZQ7zB5KSwcLZpiIaeHDsCZrKfona3jQuFIEOmjGg0khXIUQvbIdS/i6FjUUzFPJmGlwgxR7Of3kkMfQCxzNYxmAj+mJqxEChLfRH4aRyajlGa4/Dcu5Jse8e+v8o82nkHqxOJcFJAirhVvAOjkbayGeYn91oFc5IVUl2Rj/0UIXB+kxhLhqlI/jkCIwd6MV1/ddQdBpaZHk1zcvdA962RxVDwXLi5UOI0htzgFJ1rKZQodPHE7nHdSvY6tRo/Dpj2G0TQptMJ9ty15d1aPgw2lyaulchUeTTveQr68zuu2c6Co8r5jYEHPLQyzCnsUOkwryPn2+cNK5KetHyYIQa1QZsVEDNURc0cJcj3+tywpAi+alpxZMrCb4K+NSjReBz0wnixmZRWP0j3+dDYFod/kb1XNMEaw1rr1IOCOOBPEaYWV+xI+8c/DuAwZ4jRUgnkQzxdCOcP0r/vC2AE73LeHsv--+uxudSce16/btGEI--2xMotthp7+ax9B9dI5WYxA==
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | default: &default
2 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
3 | timeout: 5000
4 |
5 | development:
6 | <<: *default
7 | adapter: postgres
8 | url: postgres://localhost:5432/resolute
9 |
10 | # Warning: The database defined as "test" will be erased and
11 | # re-generated from your development database when you run "rake".
12 | # Do not set this db to the same as development or production.
13 | test:
14 | <<: *default
15 | adapter: sqlite3
16 | database: db/test.sqlite3
17 |
18 | production:
19 | <<: *default
20 | adapter: postgres
21 | url: <%= ENV["DATABASE_URL"] %>
22 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Load the Rails application.
4 | require_relative 'application'
5 |
6 | # Initialize the Rails application.
7 | Rails.application.initialize!
8 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'active_support/core_ext/integer/time'
4 |
5 | Rails.application.configure do
6 | # Settings specified here will take precedence over those in config/application.rb.
7 |
8 | # In the development environment your application's code is reloaded any time
9 | # it changes. This slows down response time but is perfect for development
10 | # since you don't have to restart the web server when you make code changes.
11 | config.cache_classes = false
12 |
13 | # Do not eager load code on boot.
14 | config.eager_load = false
15 |
16 | # Show full error reports.
17 | config.consider_all_requests_local = true
18 |
19 | # Enable server timing
20 | config.server_timing = true
21 |
22 | # Enable/disable caching. By default caching is disabled.
23 | # Run rails dev:cache to toggle caching.
24 | if Rails.root.join('tmp/caching-dev.txt').exist?
25 | config.action_controller.perform_caching = true
26 | config.action_controller.enable_fragment_cache_logging = true
27 |
28 | config.cache_store = :memory_store
29 | config.public_file_server.headers = {
30 | 'Cache-Control' => "public, max-age=#{2.days.to_i}"
31 | }
32 | else
33 | config.action_controller.perform_caching = false
34 |
35 | config.cache_store = :null_store
36 | end
37 |
38 | # Store uploaded files on the local file system (see config/storage.yml for options).
39 | config.active_storage.service = :local
40 |
41 | # Don't care if the mailer can't send.
42 | config.action_mailer.raise_delivery_errors = false
43 |
44 | config.action_mailer.perform_caching = false
45 |
46 | config.action_mailer.delivery_method = :smtp
47 | config.action_mailer.smtp_settings = {
48 | port: 1025
49 | }
50 |
51 | config.action_mailer.default_url_options = { host: 'localhost:3000' }
52 |
53 | # Print deprecation notices to the Rails logger.
54 | config.active_support.deprecation = :log
55 |
56 | # Raise exceptions for disallowed deprecations.
57 | config.active_support.disallowed_deprecation = :raise
58 |
59 | # Tell Active Support which deprecation messages to disallow.
60 | config.active_support.disallowed_deprecation_warnings = []
61 |
62 | # Raise an error on page load if there are pending migrations.
63 | config.active_record.migration_error = :page_load
64 |
65 | # Highlight code that triggered database queries in logs.
66 | config.active_record.verbose_query_logs = true
67 |
68 | # Suppress logger output for asset requests.
69 | config.assets.quiet = true
70 |
71 | # Raises error for missing translations.
72 | # config.i18n.raise_on_missing_translations = true
73 |
74 | # Annotate rendered view with file names.
75 | # config.action_view.annotate_rendered_view_with_filenames = true
76 |
77 | # Uncomment if you wish to allow Action Cable access from any origin.
78 | # config.action_cable.disable_request_forgery_protection = true
79 | end
80 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'active_support/core_ext/integer/time'
4 |
5 | Rails.application.configure do
6 | # Settings specified here will take precedence over those in config/application.rb.
7 |
8 | # Code is not reloaded between requests.
9 | config.cache_classes = true
10 |
11 | # Eager load code on boot. This eager loads most of Rails and
12 | # your application in memory, allowing both threaded web servers
13 | # and those relying on copy on write to perform better.
14 | # Rake tasks automatically ignore this option for performance.
15 | config.eager_load = true
16 |
17 | # Full error reports are disabled and caching is turned on.
18 | config.consider_all_requests_local = false
19 | config.action_controller.perform_caching = true
20 |
21 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
22 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
23 | config.require_master_key = false
24 |
25 | # Disable serving static files from the `/public` folder by default since
26 | # Apache or NGINX already handles this.
27 | config.public_file_server.enabled = true
28 |
29 | # Compress CSS using a preprocessor.
30 | # config.assets.css_compressor = :sass
31 |
32 | # Do not fallback to assets pipeline if a precompiled asset is missed.
33 | config.assets.compile = false
34 |
35 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
36 | # config.asset_host = "http://assets.example.com"
37 |
38 | # Specifies the header that your server uses for sending files.
39 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
40 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX
41 |
42 | # Store uploaded files on the local file system (see config/storage.yml for options).
43 | config.active_storage.service = :local
44 |
45 | # Mount Action Cable outside main process or domain.
46 | # config.action_cable.mount_path = nil
47 | # config.action_cable.url = "wss://example.com/cable"
48 | # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ]
49 |
50 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
51 | # config.force_ssl = true
52 |
53 | # Include generic and useful information about system operation, but avoid logging too much
54 | # information to avoid inadvertent exposure of personally identifiable information (PII).
55 | config.log_level = :info
56 |
57 | # Prepend all log lines with the following tags.
58 | config.log_tags = [:request_id]
59 |
60 | # Use a different cache store in production.
61 | # config.cache_store = :mem_cache_store
62 |
63 | # Use a real queuing backend for Active Job (and separate queues per environment).
64 | # config.active_job.queue_adapter = :resque
65 | # config.active_job.queue_name_prefix = "resolute_production"
66 |
67 | config.action_mailer.perform_caching = false
68 |
69 | # Ignore bad email addresses and do not raise email delivery errors.
70 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
71 | # config.action_mailer.raise_delivery_errors = false
72 |
73 | config.action_mailer.delivery_method = :smtp
74 | config.action_mailer.smtp_settings = {
75 | address: Rails.application.credentials.dig(:mail, :address),
76 | user_name: Rails.application.credentials.dig(:mail, :username),
77 | password: Rails.application.credentials.dig(:mail, :password)
78 | }
79 |
80 | config.action_mailer.default_url_options = { host: 'useresolute.com' }
81 |
82 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
83 | # the I18n.default_locale when a translation cannot be found).
84 | config.i18n.fallbacks = true
85 |
86 | # Don't log any deprecations.
87 | config.active_support.report_deprecations = false
88 |
89 | # Use default logging formatter so that PID and timestamp are not suppressed.
90 | config.log_formatter = ::Logger::Formatter.new
91 |
92 | # Use a different logger for distributed setups.
93 | # require "syslog/logger"
94 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name")
95 |
96 | if ENV['RAILS_LOG_TO_STDOUT'].present?
97 | logger = ActiveSupport::Logger.new($stdout)
98 | logger.formatter = config.log_formatter
99 | config.logger = ActiveSupport::TaggedLogging.new(logger)
100 | end
101 |
102 | # Do not dump schema after migrations.
103 | config.active_record.dump_schema_after_migration = false
104 | end
105 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'active_support/core_ext/integer/time'
4 |
5 | # The test environment is used exclusively to run your application's
6 | # test suite. You never need to work with it otherwise. Remember that
7 | # your test database is "scratch space" for the test suite and is wiped
8 | # and recreated between test runs. Don't rely on the data there!
9 |
10 | Rails.application.configure do
11 | # Settings specified here will take precedence over those in config/application.rb.
12 |
13 | # Turn false under Spring and add config.action_view.cache_template_loading = true.
14 | config.cache_classes = true
15 |
16 | # Eager loading loads your whole application. When running a single test locally,
17 | # this probably isn't necessary. It's a good idea to do in a continuous integration
18 | # system, or in some way before deploying your code.
19 | config.eager_load = ENV['CI'].present?
20 |
21 | # Configure public file server for tests with Cache-Control for performance.
22 | config.public_file_server.enabled = true
23 | config.public_file_server.headers = {
24 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}"
25 | }
26 |
27 | # Show full error reports and disable caching.
28 | config.consider_all_requests_local = true
29 | config.action_controller.perform_caching = false
30 | config.cache_store = :null_store
31 |
32 | # Raise exceptions instead of rendering exception templates.
33 | config.action_dispatch.show_exceptions = false
34 |
35 | # Disable request forgery protection in test environment.
36 | config.action_controller.allow_forgery_protection = false
37 |
38 | # Store uploaded files on the local file system in a temporary directory.
39 | config.active_storage.service = :test
40 |
41 | config.action_mailer.perform_caching = false
42 |
43 | # Tell Action Mailer not to deliver emails to the real world.
44 | # The :test delivery method accumulates sent emails in the
45 | # ActionMailer::Base.deliveries array.
46 | config.action_mailer.delivery_method = :test
47 |
48 | # Print deprecation notices to the stderr.
49 | config.active_support.deprecation = :stderr
50 |
51 | # Raise exceptions for disallowed deprecations.
52 | config.active_support.disallowed_deprecation = :raise
53 |
54 | # Tell Active Support which deprecation messages to disallow.
55 | config.active_support.disallowed_deprecation_warnings = []
56 |
57 | # Raises error for missing translations.
58 | # config.i18n.raise_on_missing_translations = true
59 |
60 | # Annotate rendered view with file names.
61 | # config.action_view.annotate_rendered_view_with_filenames = true
62 | end
63 |
--------------------------------------------------------------------------------
/config/honeybadger.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # For more options, see https://docs.honeybadger.io/lib/ruby/gem-reference/configuration
3 |
4 | api_key: "<%= Rails.application.credentials.honeybadger_api_key %>"
5 |
6 | # The environment your app is running in.
7 | env: "<%= Rails.env %>"
8 |
9 | # The absolute path to your project folder.
10 | root: "<%= Rails.root.to_s %>"
11 |
12 | # Honeybadger won't report errors in these environments.
13 | development_environments:
14 | - test
15 | - development
16 | - cucumber
17 |
18 | # By default, Honeybadger won't report errors in the development_environments.
19 | # You can override this by explicitly setting report_data to true or false.
20 | # report_data: true
21 |
22 | # The current Git revision of your project. Defaults to the last commit hash.
23 | # revision: null
24 |
25 | # Enable verbose debug logging (useful for troubleshooting).
26 | debug: false
27 |
--------------------------------------------------------------------------------
/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Be sure to restart your server when you modify this file.
3 |
4 | # ActiveSupport::Reloader.to_prepare do
5 | # ApplicationController.renderer.defaults.merge!(
6 | # http_host: 'example.org',
7 | # https: false
8 | # )
9 | # end
10 |
--------------------------------------------------------------------------------
/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Version of your assets, change this if you want to expire all your assets.
6 | Rails.application.config.assets.version = '1.0'
7 |
8 | # Add additional assets to the asset load path.
9 | # Rails.application.config.assets.paths << Emoji.images_path
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 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
6 | # Rails.backtrace_cleaner.add_silencer { |line| /my_noisy_library/.match?(line) }
7 |
8 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code
9 | # by setting BACKTRACE=1 before calling your invocation, like "BACKTRACE=1 ./bin/rails runner 'MyClass.perform'".
10 | Rails.backtrace_cleaner.remove_silencers! if ENV['BACKTRACE']
11 |
--------------------------------------------------------------------------------
/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Be sure to restart your server when you modify this file.
3 |
4 | # Define an application-wide content security policy
5 | # For further information see the following documentation
6 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
7 |
8 | # Rails.application.configure do
9 | # config.content_security_policy do |policy|
10 | # policy.default_src :self, :https
11 | # policy.font_src :self, :https, :data
12 | # policy.img_src :self, :https, :data
13 | # policy.object_src :none
14 | # policy.script_src :self, :https
15 | # policy.style_src :self, :https
16 | # # Specify URI for violation reports
17 | # # policy.report_uri "/csp-violation-report-endpoint"
18 | # end
19 | #
20 | # # Generate session nonces for permitted importmap and inline scripts
21 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
22 | # config.content_security_policy_nonce_directives = %w(script-src)
23 | #
24 | # # Report CSP violations to a specified URI. See:
25 | # # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
26 | # # config.content_security_policy_report_only = true
27 | # end
28 |
--------------------------------------------------------------------------------
/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Specify a serializer for the signed and encrypted cookie jars.
6 | # Valid options are :json, :marshal, and :hybrid.
7 | Rails.application.config.action_dispatch.cookies_serializer = :json
8 |
--------------------------------------------------------------------------------
/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Configure sensitive parameters which will be filtered from the log file.
6 | Rails.application.config.filter_parameters += %i[
7 | passw secret token _key crypt salt certificate otp ssn
8 | ]
9 |
--------------------------------------------------------------------------------
/config/initializers/honeybadger.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Honeybadger.configure do |config|
4 | config.before_notify do |notice|
5 | # Change "errors" to match your custom controller name.
6 | break if notice.component != 'errors'
7 |
8 | # Look up original route path and override controller/action
9 | # in Honeybadger.
10 | params = Rails.application.routes.recognize_path(notice.url)
11 | notice.component = params[:controller]
12 | notice.action = params[:action]
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Be sure to restart your server when you modify this file.
3 |
4 | # Add new inflection rules using the following format. Inflections
5 | # are locale specific, and you may define rules for as many different
6 | # locales as you wish. All of these examples are active by default:
7 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
8 | # inflect.plural /^(ox)$/i, "\\1en"
9 | # inflect.singular /^(ox)en/i, "\\1"
10 | # inflect.irregular "person", "people"
11 | # inflect.uncountable %w( fish sheep )
12 | # end
13 |
14 | # These inflection rules are supported but not enabled by default:
15 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
16 | # inflect.acronym "RESTful"
17 | # end
18 |
--------------------------------------------------------------------------------
/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Be sure to restart your server when you modify this file.
3 |
4 | # Add new mime types for use in respond_to blocks:
5 | # Mime::Type.register "text/richtext", :rtf
6 |
--------------------------------------------------------------------------------
/config/initializers/permissions_policy.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Define an application-wide HTTP permissions policy. For further
3 | # information see https://developers.google.com/web/updates/2018/06/feature-policy
4 | #
5 | # Rails.application.config.permissions_policy do |f|
6 | # f.camera :none
7 | # f.gyroscope :none
8 | # f.microphone :none
9 | # f.usb :none
10 | # f.fullscreen :self
11 | # f.payment :self, "https://secure.example.com"
12 | # end
13 |
--------------------------------------------------------------------------------
/config/initializers/redis.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | $redis = Redis::Namespace.new(
4 | :resolute,
5 | redis: Redis.new(
6 | url: ENV.fetch('REDIS_URL', 'redis://localhost:6379')
7 | )
8 | )
9 |
--------------------------------------------------------------------------------
/config/initializers/sidekiq.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Sidekiq.configure_server do |config|
4 | # config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379') }
5 | # end
6 |
7 | # Sidekiq.configure_client do |config|
8 | # config.redis = { url: ENV.fetch('REDIS_URL', 'redis://localhost:6379') }
9 | # end
10 |
--------------------------------------------------------------------------------
/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # This file contains settings for ActionController::ParamsWrapper which
6 | # is enabled by default.
7 |
8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
9 | ActiveSupport.on_load(:action_controller) do
10 | wrap_parameters format: [:json]
11 | end
12 |
13 | # To enable root element in JSON for ActiveRecord objects.
14 | # ActiveSupport.on_load(:active_record) do
15 | # self.include_root_in_json = true
16 | # end
17 |
--------------------------------------------------------------------------------
/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 | # frozen_string_literal: true
2 |
3 | # Puma can serve each request in a thread from an internal thread pool.
4 | # The `threads` method setting takes two numbers: a minimum and maximum.
5 | # Any libraries that use thread pools should be configured to match
6 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
7 | # and maximum; this matches the default thread size of Active Record.
8 | #
9 | max_threads_count = ENV.fetch('RAILS_MAX_THREADS', 5)
10 | min_threads_count = ENV.fetch('RAILS_MIN_THREADS') { max_threads_count }
11 | threads min_threads_count, max_threads_count
12 |
13 | # Specifies the `worker_timeout` threshold that Puma will use to wait before
14 | # terminating a worker in development environments.
15 | #
16 | worker_timeout 3600 if ENV.fetch('RAILS_ENV', 'development') == 'development'
17 |
18 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
19 | #
20 | port ENV.fetch('PORT', 3000)
21 |
22 | # Specifies the `environment` that Puma will run in.
23 | #
24 | environment ENV.fetch('RAILS_ENV', 'development')
25 |
26 | # Specifies the `pidfile` that Puma will use.
27 | pidfile ENV.fetch('PIDFILE', 'tmp/pids/server.pid')
28 |
29 | # Specifies the number of `workers` to boot in clustered mode.
30 | # Workers are forked web server processes. If using threads and workers together
31 | # the concurrency of the application would be max `threads` * `workers`.
32 | # Workers do not work on JRuby or Windows (both of which do not support
33 | # processes).
34 | #
35 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
36 |
37 | # Use the `preload_app!` method when specifying a `workers` number.
38 | # This directive tells Puma to first boot the application and load code
39 | # before forking the application. This takes advantage of Copy On Write
40 | # process behavior so workers use less memory.
41 | #
42 | # preload_app!
43 |
44 | # Allow puma to be restarted by `rails restart` command.
45 | plugin :tmp_restart
46 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Rails.application.routes.draw do
4 | # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
5 | root to: 'landing#index'
6 |
7 | get 'signup', to: 'signup#index'
8 | post 'signup', to: 'signup#submit'
9 | get 'verify', to: 'signup#verify'
10 | post 'verify', to: 'signup#finalize'
11 |
12 | get 'forgot-password', to: 'auth#forgot_password'
13 | post 'forgot-password', to: 'auth#forgot_password_submit'
14 | get 'forgot-password/verify', to: 'auth#forgot_password_verify'
15 | post 'forgot-password/verify', to: 'auth#forgot_password_finalize'
16 |
17 | scope '/callback' do
18 | get 'github', to: 'oauth_callback#github'
19 | end
20 |
21 | get 'login', to: 'auth#login'
22 | post 'login', to: 'auth#auth'
23 |
24 | post 'logout', to: 'auth#logout'
25 |
26 | get 'home', to: 'reminders#index'
27 | resources :reminders, only: %i[create destroy]
28 |
29 | resources :integrations, only: %i[index]
30 |
31 | scope '/settings' do
32 | get '/', to: 'settings#index'
33 | post '/', to: 'settings#update'
34 | get 'security', to: 'settings#security'
35 |
36 | resources :sessions, path: 'security/sessions', only: %i[destroy] do
37 | collection do
38 | post 'destroy_all'
39 | end
40 | end
41 |
42 | resources :authorizations, path: 'security/authorizations', only: %i[destroy]
43 | end
44 |
45 | scope '/developer' do
46 | get '/', to: 'settings#developer'
47 |
48 | resources :oauth_apps, path: 'apps', path_names: { edit: 'settings' } do
49 | member do
50 | get 'advanced'
51 | end
52 | end
53 |
54 | resources :personal_tokens, path: 'tokens', only: %i[index new create destroy]
55 | end
56 |
57 | scope '/hovercard' do
58 | get 'unfurl', to: 'hovercard#unfurl'
59 | end
60 |
61 | match '/404', to: 'errors#not_found', via: :all
62 | match '/500', to: 'errors#internal_server_error', via: :all
63 |
64 | # load routes/api.rb
65 | draw :api
66 | end
67 |
--------------------------------------------------------------------------------
/config/routes/api.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | namespace :api do
4 | scope '/oauth' do
5 | get 'authorize', to: 'oauth#authorize'
6 | post 'authorize', to: 'oauth#authorize_submit'
7 | post 'token', to: 'oauth#token'
8 | end
9 |
10 | defaults format: :json do
11 | get 'me', to: 'users#me'
12 | resources :reminders, only: %i[index create destroy]
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/config/spring.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Spring.watch(
4 | '.ruby-version',
5 | '.rbenv-vars',
6 | 'tmp/restart.txt',
7 | 'tmp/caching-dev.txt'
8 | )
9 |
--------------------------------------------------------------------------------
/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 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
9 | # amazon:
10 | # service: S3
11 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
12 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
13 | # region: us-east-1
14 | # bucket: your_own_bucket
15 |
16 | # Remember not to checkin your GCS keyfile to a repository
17 | # google:
18 | # service: GCS
19 | # project: your_project
20 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
21 | # bucket: your_own_bucket
22 |
23 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
24 | # microsoft:
25 | # service: AzureStorage
26 | # storage_account_name: your_account_name
27 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
28 | # container: your_container_name
29 |
30 | # mirror:
31 | # service: Mirror
32 | # primary: local
33 | # mirrors: [ amazon, google, microsoft ]
34 |
--------------------------------------------------------------------------------
/db/migrate/20211111021027_create_users.rb:
--------------------------------------------------------------------------------
1 | class CreateUsers < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :users do |t|
4 | t.string :email
5 | t.string :hashed_password
6 |
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/db/migrate/20211111022726_rename_hashed_password.rb:
--------------------------------------------------------------------------------
1 | class RenameHashedPassword < ActiveRecord::Migration[6.1]
2 | def change
3 | change_table :users do |t|
4 | t.rename :hashed_password, :password_digest
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/db/migrate/20211111041143_create_sessions.rb:
--------------------------------------------------------------------------------
1 | class CreateSessions < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :sessions do |t|
4 | t.string :token
5 | t.references :user, null: false, foreign_key: true
6 |
7 | t.timestamps
8 | end
9 | add_index :sessions, :token, unique: true
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20211111052623_create_reminders.rb:
--------------------------------------------------------------------------------
1 | class CreateReminders < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :reminders do |t|
4 | t.references :user, null: false, foreign_key: true
5 | t.string :title
6 |
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/db/migrate/20211119030654_create_signup_requests.rb:
--------------------------------------------------------------------------------
1 | class CreateSignupRequests < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :signup_requests do |t|
4 | t.string :email, null: false
5 | t.string :code, null: false
6 | t.boolean :fulfilled, default: false, null: false
7 | t.datetime :expires_at, null: false
8 |
9 | t.timestamps
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/db/migrate/20211125033049_unique_fields.rb:
--------------------------------------------------------------------------------
1 | class UniqueFields < ActiveRecord::Migration[6.1]
2 | def change
3 | add_index :users, :email, unique: true
4 | add_index :signup_requests, :code, unique: true
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20211125044511_create_oauth_apps.rb:
--------------------------------------------------------------------------------
1 | class CreateOauthApps < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :oauth_apps do |t|
4 | t.references :user, null: false, foreign_key: true
5 | t.string :client_id
6 | t.string :client_secret
7 | t.string :redirect_uri
8 |
9 | t.timestamps
10 | end
11 | add_index :oauth_apps, :client_id, unique: true
12 | add_index :oauth_apps, :client_secret, unique: true
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/db/migrate/20211125050029_add_name_to_oauth_app.rb:
--------------------------------------------------------------------------------
1 | class AddNameToOauthApp < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :oauth_apps, :name, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20211126050826_add_official_to_oauth_app.rb:
--------------------------------------------------------------------------------
1 | class AddOfficialToOauthApp < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :oauth_apps, :official, :boolean
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20211128041643_create_oauth_grants.rb:
--------------------------------------------------------------------------------
1 | class CreateOauthGrants < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :oauth_grants do |t|
4 | t.string :code
5 | t.string :scope, array: true
6 | t.references :user, null: false, foreign_key: true
7 | t.references :oauth_app, null: false, foreign_key: true
8 |
9 | t.timestamps
10 | end
11 | add_index :oauth_grants, :code, unique: true
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/db/migrate/20211128044302_add_expires_at_to_oauth_grant.rb:
--------------------------------------------------------------------------------
1 | class AddExpiresAtToOauthGrant < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :oauth_grants, :expires_at, :datetime
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20211128191000_add_fulfilled_to_oauth_grants.rb:
--------------------------------------------------------------------------------
1 | class AddFulfilledToOauthGrants < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :oauth_grants, :fulfilled, :boolean, default: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20211129020838_create_api_tokens.rb:
--------------------------------------------------------------------------------
1 | class CreateApiTokens < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :api_tokens do |t|
4 | t.references :oauth_app, null: false, foreign_key: true
5 | t.references :user, null: false, foreign_key: true
6 | t.string :token
7 | t.string :scope, array: true
8 |
9 | t.timestamps
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/db/migrate/20211201171829_add_oauth_app_to_reminders.rb:
--------------------------------------------------------------------------------
1 | class AddOauthAppToReminders < ActiveRecord::Migration[6.1]
2 | def change
3 | add_reference :reminders, :oauth_app, null: true, foreign_key: true
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20211201172253_reminder_oauth_app_optional.rb:
--------------------------------------------------------------------------------
1 | class ReminderOauthAppOptional < ActiveRecord::Migration[6.1]
2 | def change
3 | change_column_null :reminders, :oauth_app_id, true
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20211202020630_add_ip_and_user_agent_to_sessions.rb:
--------------------------------------------------------------------------------
1 | class AddIpAndUserAgentToSessions < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :sessions, :ip, :string
4 | add_column :sessions, :user_agent, :string
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20211202174211_add_login_method_to_session.rb:
--------------------------------------------------------------------------------
1 | class AddLoginMethodToSession < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :sessions, :login_method, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20211202225129_add_description_to_api_token.rb:
--------------------------------------------------------------------------------
1 | class AddDescriptionToApiToken < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :api_tokens, :description, :string
4 | change_column_null :api_tokens, :oauth_app_id, true
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20211207152821_add_public_to_oauth_app.rb:
--------------------------------------------------------------------------------
1 | class AddPublicToOauthApp < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :oauth_apps, :public, :boolean
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20211209041857_create_password_reset_requests.rb:
--------------------------------------------------------------------------------
1 | class CreatePasswordResetRequests < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :password_reset_requests do |t|
4 | t.references :user, null: false, foreign_key: true
5 | t.string :code
6 | t.boolean :fulfilled
7 | t.datetime :expires_at
8 |
9 | t.timestamps
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/db/migrate/20211214205049_password_reset_request_fulfilled_default.rb:
--------------------------------------------------------------------------------
1 | class PasswordResetRequestFulfilledDefault < ActiveRecord::Migration[6.1]
2 | def change
3 | change_column_default :password_reset_requests, :fulfilled, false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20211215035648_create_active_storage_tables.active_storage.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from active_storage (originally 20170806125915)
2 | class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
3 | def change
4 | create_table :active_storage_blobs do |t|
5 | t.string :key, null: false
6 | t.string :filename, null: false
7 | t.string :content_type
8 | t.text :metadata
9 | t.string :service_name, null: false
10 | t.bigint :byte_size, null: false
11 | t.string :checksum, null: false
12 | t.datetime :created_at, null: false
13 |
14 | t.index [ :key ], unique: true
15 | end
16 |
17 | create_table :active_storage_attachments do |t|
18 | t.string :name, null: false
19 | t.references :record, null: false, polymorphic: true, index: false
20 | t.references :blob, null: false
21 |
22 | t.datetime :created_at, null: false
23 |
24 | t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
25 | t.foreign_key :active_storage_blobs, column: :blob_id
26 | end
27 |
28 | create_table :active_storage_variant_records do |t|
29 | t.belongs_to :blob, null: false, index: false
30 | t.string :variation_digest, null: false
31 |
32 | t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
33 | t.foreign_key :active_storage_blobs, column: :blob_id
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/db/migrate/20211217022916_add_fields_to_reminder.rb:
--------------------------------------------------------------------------------
1 | class AddFieldsToReminder < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :reminders, :description, :text
4 | add_column :reminders, :author_name, :text
5 | add_column :reminders, :author_avatar, :text
6 | add_column :reminders, :url, :string
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/db/migrate/20211217023036_author_name_to_author.rb:
--------------------------------------------------------------------------------
1 | class AuthorNameToAuthor < ActiveRecord::Migration[6.1]
2 | def change
3 | rename_column :reminders, :author_name, :author
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20211217030224_title_to_description.rb:
--------------------------------------------------------------------------------
1 | class TitleToDescription < ActiveRecord::Migration[6.1]
2 | def change
3 | Reminder.connection.execute('UPDATE reminders SET description = title, title = NULL')
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20211219051154_reminder_description_to_text.rb:
--------------------------------------------------------------------------------
1 | class ReminderDescriptionToText < ActiveRecord::Migration[6.1]
2 | def change
3 | rename_column :reminders, :description, :text
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20211229214208_add_installation_url_to_oauth_apps.rb:
--------------------------------------------------------------------------------
1 | class AddInstallationUrlToOauthApps < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :oauth_apps, :installation_url, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20220122173730_add_service_name_to_active_storage_blobs.active_storage.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from active_storage (originally 20190112182829)
2 | class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]
3 | def up
4 | unless column_exists?(:active_storage_blobs, :service_name)
5 | add_column :active_storage_blobs, :service_name, :string
6 |
7 | if configured_service = ActiveStorage::Blob.service.name
8 | ActiveStorage::Blob.unscoped.update_all(service_name: configured_service)
9 | end
10 |
11 | change_column :active_storage_blobs, :service_name, :string, null: false
12 | end
13 | end
14 |
15 | def down
16 | remove_column :active_storage_blobs, :service_name
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/db/migrate/20220122173731_create_active_storage_variant_records.active_storage.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from active_storage (originally 20191206030411)
2 | class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]
3 | def change
4 | # Use Active Record's configured type for primary key
5 | create_table :active_storage_variant_records, id: primary_key_type, if_not_exists: true do |t|
6 | t.belongs_to :blob, null: false, index: false, type: blobs_primary_key_type
7 | t.string :variation_digest, null: false
8 |
9 | t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
10 | t.foreign_key :active_storage_blobs, column: :blob_id
11 | end
12 | end
13 |
14 | private
15 | def primary_key_type
16 | config = Rails.configuration.generators
17 | config.options[config.orm][:primary_key_type] || :primary_key
18 | end
19 |
20 | def blobs_primary_key_type
21 | pkey_name = connection.primary_key(:active_storage_blobs)
22 | pkey_column = connection.columns(:active_storage_blobs).find { |c| c.name == pkey_name }
23 | pkey_column.bigint? ? :bigint : pkey_column.type
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/db/migrate/20220122173732_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from active_storage (originally 20211119233751)
2 | class RemoveNotNullOnActiveStorageBlobsChecksum < ActiveRecord::Migration[6.0]
3 | def change
4 | change_column_null(:active_storage_blobs, :checksum, true)
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20220123025226_add_source_to_reminder.rb:
--------------------------------------------------------------------------------
1 | class AddSourceToReminder < ActiveRecord::Migration[7.0]
2 | def change
3 | add_column :reminders, :source, :text
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20220224233219_add_discarded_at_to_oauth_apps.rb:
--------------------------------------------------------------------------------
1 | class AddDiscardedAtToOauthApps < ActiveRecord::Migration[7.0]
2 | def change
3 | add_column :oauth_apps, :discarded_at, :datetime
4 | add_index :oauth_apps, :discarded_at
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20220320193230_add_last_active_at_to_session.rb:
--------------------------------------------------------------------------------
1 | class AddLastActiveAtToSession < ActiveRecord::Migration[7.0]
2 | def change
3 | add_column :sessions, :last_active_at, :datetime
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/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 `bin/rails
6 | # db:schema:load`. When creating a new database, `bin/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: 2022_03_20_193230) do
14 |
15 | # These are extensions that must be enabled in order to support this database
16 | enable_extension "plpgsql"
17 |
18 | create_table "active_storage_attachments", force: :cascade do |t|
19 | t.string "name", null: false
20 | t.string "record_type", null: false
21 | t.bigint "record_id", null: false
22 | t.bigint "blob_id", null: false
23 | t.datetime "created_at", null: false
24 | t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
25 | t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
26 | end
27 |
28 | create_table "active_storage_blobs", force: :cascade do |t|
29 | t.string "key", null: false
30 | t.string "filename", null: false
31 | t.string "content_type"
32 | t.text "metadata"
33 | t.string "service_name", null: false
34 | t.bigint "byte_size", null: false
35 | t.string "checksum"
36 | t.datetime "created_at", null: false
37 | t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
38 | end
39 |
40 | create_table "active_storage_variant_records", force: :cascade do |t|
41 | t.bigint "blob_id", null: false
42 | t.string "variation_digest", null: false
43 | t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
44 | end
45 |
46 | create_table "api_tokens", force: :cascade do |t|
47 | t.bigint "oauth_app_id"
48 | t.bigint "user_id", null: false
49 | t.string "token"
50 | t.string "scope", array: true
51 | t.datetime "created_at", precision: 6, null: false
52 | t.datetime "updated_at", precision: 6, null: false
53 | t.string "description"
54 | t.index ["oauth_app_id"], name: "index_api_tokens_on_oauth_app_id"
55 | t.index ["user_id"], name: "index_api_tokens_on_user_id"
56 | end
57 |
58 | create_table "oauth_apps", force: :cascade do |t|
59 | t.bigint "user_id", null: false
60 | t.string "client_id"
61 | t.string "client_secret"
62 | t.string "redirect_uri"
63 | t.datetime "created_at", precision: 6, null: false
64 | t.datetime "updated_at", precision: 6, null: false
65 | t.string "name"
66 | t.boolean "official"
67 | t.boolean "public"
68 | t.string "installation_url"
69 | t.datetime "discarded_at", precision: 6
70 | t.index ["client_id"], name: "index_oauth_apps_on_client_id", unique: true
71 | t.index ["client_secret"], name: "index_oauth_apps_on_client_secret", unique: true
72 | t.index ["discarded_at"], name: "index_oauth_apps_on_discarded_at"
73 | t.index ["user_id"], name: "index_oauth_apps_on_user_id"
74 | end
75 |
76 | create_table "oauth_grants", force: :cascade do |t|
77 | t.string "code"
78 | t.string "scope", array: true
79 | t.bigint "user_id", null: false
80 | t.bigint "oauth_app_id", null: false
81 | t.datetime "created_at", precision: 6, null: false
82 | t.datetime "updated_at", precision: 6, null: false
83 | t.datetime "expires_at"
84 | t.boolean "fulfilled", default: false
85 | t.index ["code"], name: "index_oauth_grants_on_code", unique: true
86 | t.index ["oauth_app_id"], name: "index_oauth_grants_on_oauth_app_id"
87 | t.index ["user_id"], name: "index_oauth_grants_on_user_id"
88 | end
89 |
90 | create_table "password_reset_requests", force: :cascade do |t|
91 | t.bigint "user_id", null: false
92 | t.string "code"
93 | t.boolean "fulfilled", default: false
94 | t.datetime "expires_at"
95 | t.datetime "created_at", precision: 6, null: false
96 | t.datetime "updated_at", precision: 6, null: false
97 | t.index ["user_id"], name: "index_password_reset_requests_on_user_id"
98 | end
99 |
100 | create_table "reminders", force: :cascade do |t|
101 | t.bigint "user_id", null: false
102 | t.string "title"
103 | t.datetime "created_at", precision: 6, null: false
104 | t.datetime "updated_at", precision: 6, null: false
105 | t.bigint "oauth_app_id"
106 | t.text "text"
107 | t.text "author"
108 | t.text "author_avatar"
109 | t.string "url"
110 | t.text "source"
111 | t.index ["oauth_app_id"], name: "index_reminders_on_oauth_app_id"
112 | t.index ["user_id"], name: "index_reminders_on_user_id"
113 | end
114 |
115 | create_table "sessions", force: :cascade do |t|
116 | t.string "token"
117 | t.bigint "user_id", null: false
118 | t.datetime "created_at", precision: 6, null: false
119 | t.datetime "updated_at", precision: 6, null: false
120 | t.string "ip"
121 | t.string "user_agent"
122 | t.string "login_method"
123 | t.datetime "last_active_at", precision: 6
124 | t.index ["token"], name: "index_sessions_on_token", unique: true
125 | t.index ["user_id"], name: "index_sessions_on_user_id"
126 | end
127 |
128 | create_table "signup_requests", force: :cascade do |t|
129 | t.string "email", null: false
130 | t.string "code", null: false
131 | t.boolean "fulfilled", default: false, null: false
132 | t.datetime "expires_at", null: false
133 | t.datetime "created_at", precision: 6, null: false
134 | t.datetime "updated_at", precision: 6, null: false
135 | t.index ["code"], name: "index_signup_requests_on_code", unique: true
136 | end
137 |
138 | create_table "users", force: :cascade do |t|
139 | t.string "email"
140 | t.string "password_digest"
141 | t.datetime "created_at", precision: 6, null: false
142 | t.datetime "updated_at", precision: 6, null: false
143 | t.index ["email"], name: "index_users_on_email", unique: true
144 | end
145 |
146 | add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
147 | add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
148 | add_foreign_key "api_tokens", "oauth_apps"
149 | add_foreign_key "api_tokens", "users"
150 | add_foreign_key "oauth_apps", "users"
151 | add_foreign_key "oauth_grants", "oauth_apps"
152 | add_foreign_key "oauth_grants", "users"
153 | add_foreign_key "password_reset_requests", "users"
154 | add_foreign_key "reminders", "oauth_apps"
155 | add_foreign_key "reminders", "users"
156 | add_foreign_key "sessions", "users"
157 | end
158 |
--------------------------------------------------------------------------------
/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 bin/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 |
--------------------------------------------------------------------------------
/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/lib/assets/.keep
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/lib/tasks/.keep
--------------------------------------------------------------------------------
/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/log/.keep
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "resolute",
3 | "private": true,
4 | "version": "0.1.0",
5 | "dependencies": {
6 | "@alpinejs/collapse": "^3.8.1",
7 | "@alpinejs/persist": "^3.7.1",
8 | "@floating-ui/dom": "^0.2.0",
9 | "@honeybadger-io/js": "^3.2.8",
10 | "@hotwired/stimulus": "^3.1.0",
11 | "@hotwired/turbo-rails": "^7.2.0",
12 | "@rails/actioncable": "^7.0.1",
13 | "@rails/activestorage": "^7.0.1",
14 | "@yarnpkg/esbuild-plugin-pnp": "^2.0.0",
15 | "alpinejs": "^3.7.0",
16 | "autoprefixer": "^10.4.0",
17 | "cssnano": "^5.0.12",
18 | "esbuild": "^0.14.2",
19 | "postcss": "^8.4.4",
20 | "postcss-cli": "^9.0.2",
21 | "postcss-import": "^14.0.2",
22 | "tailwindcss": "^3.0.0",
23 | "typescript": "^4.4.4"
24 | },
25 | "scripts": {
26 | "build": "bin/esbuild",
27 | "build:css": "postcss ./app/assets/stylesheets/application.css -o ./app/assets/builds/application.css",
28 | "ts-check": "tsc --noEmit",
29 | "format:check": "prettier --check app/javascript/ app/assets/stylesheets/",
30 | "format:fix": "prettier --write app/javascript/ app/assets/stylesheets/",
31 | "lint": "eslint app/javascript/"
32 | },
33 | "devDependencies": {
34 | "@types/alpinejs": "^3.7.0",
35 | "@types/rails__actioncable": "^6.1.6",
36 | "@types/tailwindcss": "^3.0.3",
37 | "@typescript-eslint/eslint-plugin": "^5.6.0",
38 | "@typescript-eslint/parser": "^5.6.0",
39 | "eslint": "^8.4.1",
40 | "prettier": "^2.5.1"
41 | },
42 | "packageManager": "yarn@3.1.1"
43 | }
44 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require("tailwindcss/nesting"),
4 | require("tailwindcss"),
5 | require("autoprefixer"),
6 | ...(process.env.NODE_ENV === "production" ? [require("cssnano")] : []),
7 | ],
8 | };
9 |
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/public/favicon.ico
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
--------------------------------------------------------------------------------
/storage/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/storage/.keep
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const colors = require("tailwindcss/colors");
2 |
3 | /** @type {import("tailwindcss/tailwind-config").TailwindConfig} */
4 | module.exports = {
5 | content: [
6 | "./app/views/**/*.html.{haml,erb}",
7 | "./app/helpers/**/*.rb",
8 | "./app/javascript/controllers/*.tsx",
9 | ],
10 | theme: {
11 | extend: {
12 | fontFamily: {
13 | heading: ["Outfit", "sans-serif"],
14 | },
15 | // Fix for a Tailwind 3 change (https://tailwindcss.com/docs/upgrade-guide#removed-color-aliases)
16 | colors: {
17 | green: colors.emerald,
18 | yellow: colors.amber,
19 | purple: colors.violet,
20 | },
21 | screens: {
22 | xs: "475px",
23 | },
24 | animation: {
25 | toast: "5600ms forwards toast",
26 | },
27 | keyframes: {
28 | toast: {
29 | "0%": { opacity: "0", visibility: "visible" },
30 | "7%": { opacity: "100", visibility: "visible" },
31 | "93%": { opacity: "100", visibility: "visible" },
32 | "100%": { opacity: "0", visibility: "hidden" },
33 | },
34 | },
35 | },
36 | },
37 | };
38 |
--------------------------------------------------------------------------------
/test/application_system_test_case.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
5 | end
6 |
--------------------------------------------------------------------------------
/test/channels/application_cable/connection_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
4 | # test "connects with cookies" do
5 | # cookies.signed[:user_id] = 42
6 | #
7 | # connect
8 | #
9 | # assert_equal connection.user_id, "42"
10 | # end
11 | end
12 |
--------------------------------------------------------------------------------
/test/controllers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/test/controllers/.keep
--------------------------------------------------------------------------------
/test/controllers/auth_controller_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class AuthControllerTest < ActionDispatch::IntegrationTest
4 | test "should get login" do
5 | get auth_login_url
6 | assert_response :success
7 | end
8 |
9 | test "should get auth" do
10 | get auth_auth_url
11 | assert_response :success
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/test/controllers/authorizations_controller_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class AuthorizationsControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/errors_controller_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ErrorsControllerTest < ActionDispatch::IntegrationTest
4 | test "should get not_found" do
5 | get errors_not_found_url
6 | assert_response :success
7 | end
8 |
9 | test "should get internal_server_error" do
10 | get errors_internal_server_error_url
11 | assert_response :success
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/test/controllers/hovercard_controller_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class HovercardControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/integrations_controller_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class IntegrationsControllerTest < ActionDispatch::IntegrationTest
4 | test "should get index" do
5 | get integrations_index_url
6 | assert_response :success
7 | end
8 |
9 | test "should get show" do
10 | get integrations_show_url
11 | assert_response :success
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/test/controllers/landing_controller_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class LandingControllerTest < ActionDispatch::IntegrationTest
4 | test "should get index" do
5 | get landing_index_url
6 | assert_response :success
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/test/controllers/oauth_apps_controller_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class OauthAppsControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/oauth_callback_controller_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class OauthCallbackControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/personal_tokens_controller_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class PersonalTokensControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/reminders_controller_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class RemindersControllerTest < ActionDispatch::IntegrationTest
4 | test "should get index" do
5 | get reminders_index_url
6 | assert_response :success
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/test/controllers/settings_controller_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class SettingsControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/controllers/signup_controller_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class SignupControllerTest < ActionDispatch::IntegrationTest
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/fixtures/api_tokens.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | one:
4 | oauth_app: one
5 | user: one
6 | token: MyString
7 | scope: MyString
8 |
9 | two:
10 | oauth_app: two
11 | user: two
12 | token: MyString
13 | scope: MyString
14 |
--------------------------------------------------------------------------------
/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/test/fixtures/files/.keep
--------------------------------------------------------------------------------
/test/fixtures/oauth_apps.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | one:
4 | user: one
5 | client_id: MyString
6 | client_secret: MyString
7 | redirect_uri: MyString
8 |
9 | two:
10 | user: two
11 | client_id: MyString
12 | client_secret: MyString
13 | redirect_uri: MyString
14 |
--------------------------------------------------------------------------------
/test/fixtures/oauth_grants.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | one:
4 | code: MyString
5 | scope: MyString
6 | user: one
7 | oauth_app: one
8 |
9 | two:
10 | code: MyString
11 | scope: MyString
12 | user: two
13 | oauth_app: two
14 |
--------------------------------------------------------------------------------
/test/fixtures/password_reset_requests.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | one:
4 | user: one
5 | code: MyString
6 | fulfilled: false
7 | expires_at: 2021-12-08 23:18:57
8 |
9 | two:
10 | user: two
11 | code: MyString
12 | fulfilled: false
13 | expires_at: 2021-12-08 23:18:57
14 |
--------------------------------------------------------------------------------
/test/fixtures/reminders.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | one:
4 | user: one
5 | title: MyString
6 |
7 | two:
8 | user: two
9 | title: MyString
10 |
--------------------------------------------------------------------------------
/test/fixtures/sessions.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | one:
4 | token: MyString
5 | user: one
6 |
7 | two:
8 | token: MyString
9 | user: two
10 |
--------------------------------------------------------------------------------
/test/fixtures/signup_requests.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | one:
4 | email: MyString
5 | code: MyString
6 |
7 | two:
8 | email: MyString
9 | code: MyString
10 |
--------------------------------------------------------------------------------
/test/fixtures/users.yml:
--------------------------------------------------------------------------------
1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
2 |
3 | one:
4 | email: MyString
5 | password_digest: MyString
6 |
7 | two:
8 | email: MyString
9 | password_digest: MyString
10 |
--------------------------------------------------------------------------------
/test/helpers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/test/helpers/.keep
--------------------------------------------------------------------------------
/test/integration/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/test/integration/.keep
--------------------------------------------------------------------------------
/test/jobs/cache_link_unfurl_job_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class CacheLinkUnfurlJobTest < ActiveJob::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/test/mailers/.keep
--------------------------------------------------------------------------------
/test/mailers/previews/user_mailer_preview.rb:
--------------------------------------------------------------------------------
1 | # Preview all emails at http://localhost:3000/rails/mailers/user_mailer
2 | class UserMailerPreview < ActionMailer::Preview
3 |
4 | end
5 |
--------------------------------------------------------------------------------
/test/mailers/user_mailer_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class UserMailerTest < ActionMailer::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/test/models/.keep
--------------------------------------------------------------------------------
/test/models/api_token_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ApiTokenTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/models/oauth_app_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class OauthAppTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/models/oauth_grant_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class OauthGrantTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/models/password_reset_request_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class PasswordResetRequestTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/models/reminder_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ReminderTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/models/session_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class SessionTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/models/signup_request_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class SignupRequestTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/models/user_test.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class UserTest < ActiveSupport::TestCase
4 | # test "the truth" do
5 | # assert true
6 | # end
7 | end
8 |
--------------------------------------------------------------------------------
/test/system/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/test/system/.keep
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] ||= 'test'
2 | require_relative "../config/environment"
3 | require "rails/test_help"
4 |
5 | class ActiveSupport::TestCase
6 | # Run tests in parallel with specified workers
7 | parallelize(workers: :number_of_processors)
8 |
9 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
10 | fixtures :all
11 |
12 | # Add more helper methods to be used by all tests here...
13 | end
14 |
--------------------------------------------------------------------------------
/tmp/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/tmp/.keep
--------------------------------------------------------------------------------
/tmp/pids/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/tmp/pids/.keep
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": false,
4 | "emitDecoratorMetadata": true,
5 | "experimentalDecorators": true,
6 | "lib": ["es6", "dom"],
7 | "module": "es6",
8 | "moduleResolution": "node",
9 | "baseUrl": ".",
10 | "paths": {
11 | "*": ["node_modules/*", "app/javascript/*"]
12 | },
13 | "sourceMap": true,
14 | "target": "es6",
15 | "noEmit": true,
16 | "jsx": "react",
17 | "jsxFactory": "jsxCreateElement",
18 | "strictPropertyInitialization": false,
19 | "strictNullChecks": true
20 | },
21 | "exclude": ["**/*.spec.ts", "node_modules", "vendor", "public"],
22 | "compileOnSave": false
23 | }
24 |
--------------------------------------------------------------------------------
/vendor/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/resoluteapp/resolute/2559593003b209392f5d8f5e1ea12c3e66c74c2e/vendor/.keep
--------------------------------------------------------------------------------