├── .env.dev
├── .gitattributes
├── .gitignore
├── .ruby-version
├── Dockerfile
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── README.md
├── Rakefile
├── app
├── assets
│ ├── config
│ │ └── manifest.js
│ ├── images
│ │ └── .keep
│ └── stylesheets
│ │ └── application.css
├── channels
│ └── application_cable
│ │ ├── channel.rb
│ │ └── connection.rb
├── controllers
│ ├── api
│ │ └── v1
│ │ │ └── accounts_controller.rb
│ ├── application_controller.rb
│ ├── authentication_controller.rb
│ └── concerns
│ │ ├── .keep
│ │ └── response.rb
├── helpers
│ ├── api
│ │ └── v1
│ │ │ └── accounts_helper.rb
│ ├── application_helper.rb
│ └── authentication_helper.rb
├── interactors
│ ├── application_interactor.rb
│ ├── digital_ocean
│ │ └── poll_droplets.rb
│ ├── notion
│ │ └── poll_databases.rb
│ └── render
│ │ ├── poll_deployments.rb
│ │ ├── poll_jobs.rb
│ │ └── poll_services.rb
├── javascript
│ ├── application.js
│ └── controllers
│ │ ├── application.js
│ │ ├── hello_controller.js
│ │ └── index.js
├── jobs
│ ├── application_job.rb
│ ├── poll_accounts_job.rb
│ ├── poll_digital_ocean_resources_job.rb
│ ├── poll_notion_resources_job.rb
│ ├── poll_render_resources_job.rb
│ ├── push_webhook_to_convoy_job.rb
│ └── push_webhooks_job.rb
├── lib
│ ├── api_response
│ │ ├── account.rb
│ │ ├── auth.rb
│ │ ├── base.rb
│ │ └── user.rb
│ ├── auth
│ │ ├── authenticate_user.rb
│ │ ├── authorize_api_request.rb
│ │ ├── helper.rb
│ │ └── invalidate_api_token.rb
│ ├── exception_handler.rb
│ └── json_web_token.rb
├── mailers
│ └── application_mailer.rb
├── models
│ ├── account.rb
│ ├── application_record.rb
│ ├── concerns
│ │ ├── .keep
│ │ └── platform_configuration_validator.rb
│ ├── digital_ocean_account_configuration.rb
│ ├── digital_ocean_droplet.rb
│ ├── notion_account_configuration.rb
│ ├── notion_database.rb
│ ├── notion_row.rb
│ ├── profile.rb
│ ├── render_account_configuration.rb
│ ├── render_deployment.rb
│ ├── render_service.rb
│ ├── service.rb
│ ├── user.rb
│ ├── webhook.rb
│ ├── webhook_subscription.rb
│ └── whitelisted_jwt.rb
├── serializers
│ ├── account_serializer.rb
│ ├── application_serializer.rb
│ ├── base_model_serializer.rb
│ ├── models
│ │ ├── base_model.rb
│ │ └── registration_response.rb
│ └── registration_response_serializer.rb
└── views
│ ├── devise
│ ├── confirmations
│ │ └── new.html.erb
│ ├── mailer
│ │ ├── confirmation_instructions.html.erb
│ │ ├── email_changed.html.erb
│ │ ├── password_change.html.erb
│ │ ├── reset_password_instructions.html.erb
│ │ └── unlock_instructions.html.erb
│ ├── passwords
│ │ ├── edit.html.erb
│ │ └── new.html.erb
│ ├── registrations
│ │ ├── edit.html.erb
│ │ └── new.html.erb
│ ├── sessions
│ │ └── new.html.erb
│ ├── shared
│ │ ├── _error_messages.html.erb
│ │ └── _links.html.erb
│ └── unlocks
│ │ └── new.html.erb
│ └── layouts
│ ├── application.html.erb
│ ├── mailer.html.erb
│ └── mailer.text.erb
├── bin
├── bundle
├── importmap
├── rails
├── rake
└── setup
├── config.ru
├── config
├── application.rb
├── boot.rb
├── cable.yml
├── clock.rb
├── credentials.yml.enc
├── database.yml
├── environment.rb
├── environments
│ ├── development.rb
│ ├── production.rb
│ └── test.rb
├── importmap.rb
├── initializers
│ ├── active_model_serializers.rb
│ ├── assets.rb
│ ├── content_security_policy.rb
│ ├── convoy.rb
│ ├── devise.rb
│ ├── filter_parameter_logging.rb
│ ├── inflections.rb
│ ├── permissions_policy.rb
│ ├── sentry.rb
│ └── sidekiq.rb
├── locales
│ ├── devise.en.yml
│ └── en.yml
├── puma.rb
├── routes.rb
├── sidekiq.yml
└── storage.yml
├── db
├── migrate
│ ├── 0_enable_pgcrypto_extension.rb
│ ├── 20230617044757_create_services.rb
│ ├── 20230617105153_create_render_accounts.rb
│ ├── 20230617172518_create_render_services.rb
│ ├── 20230617205232_create_webhooks.rb
│ ├── 20230618062335_create_render_deployments.rb
│ ├── 20230618160227_create_webhook_subscriptions.rb
│ ├── 20230618170032_change_render_accounts_to_accounts.rb
│ ├── 20230618171524_create_render_account_configurations.rb
│ ├── 20230618181856_devise_create_users.rb
│ ├── 20230619080041_create_whitelisted_jwts.rb
│ ├── 20230619111134_create_profiles.rb
│ ├── 20230619140156_add_user_to_accounts.rb
│ ├── 20230621104650_add_accounts_to_webhooks.rb
│ ├── 20230621121811_add_portal_link_to_account.rb
│ ├── 20230621133250_modify_render_object_references.rb
│ ├── 20230623065032_create_notion_account_configurations.rb
│ ├── 20230623065709_create_notion_databases.rb
│ ├── 20230623070129_create_notion_rows.rb
│ ├── 20230627045713_add_fields_to_account.rb
│ ├── 20230627200008_create_digital_ocean_account_configurations.rb
│ ├── 20230627202802_create_digital_ocean_droplets.rb
│ └── 20230830142630_change_cascading_deletes.rb
├── seeds.rb
└── structure.sql
├── docker-compose.dev.yml
├── lib
├── assets
│ └── .keep
└── tasks
│ ├── .keep
│ └── auto_annotate_models.rake
├── log
└── .keep
├── public
├── 404.html
├── 422.html
├── 500.html
├── apple-touch-icon-precomposed.png
├── apple-touch-icon.png
├── favicon.ico
└── robots.txt
├── render.yaml
├── scripts
├── start.api.sh
├── start.cron.sh
└── start.worker.sh
├── spec
├── factories
│ ├── digital_ocean_account_configurations.rb
│ ├── digital_ocean_droplets.rb
│ ├── notion_account_configurations.rb
│ ├── notion_databases.rb
│ ├── notion_rows.rb
│ ├── profiles.rb
│ ├── render_account_configurations.rb
│ ├── render_accounts.rb
│ ├── render_deployments.rb
│ ├── render_services.rb
│ ├── services.rb
│ ├── users.rb
│ ├── webhook_subscriptions.rb
│ ├── webhooks.rb
│ └── whitelisted_jwts.rb
├── helpers
│ ├── api
│ │ └── v1
│ │ │ └── accounts_helper_spec.rb
│ └── authentication_helper_spec.rb
├── jobs
│ ├── poll_digital_ocean_resources_job_spec.rb
│ ├── poll_notion_resources_job_spec.rb
│ ├── poll_render_job_spec.rb
│ ├── poll_render_resources_job_spec.rb
│ ├── push_webhook_to_convoy_job_spec.rb
│ └── send_webhooks_to_convoy_job_spec.rb
├── models
│ ├── digital_ocean_account_configuration_spec.rb
│ ├── digital_ocean_droplet_spec.rb
│ ├── notion_account_configuration_spec.rb
│ ├── notion_database_spec.rb
│ ├── notion_row_spec.rb
│ ├── profile_spec.rb
│ ├── render_account_configuration_spec.rb
│ ├── render_account_spec.rb
│ ├── render_deployment_spec.rb
│ ├── render_service_spec.rb
│ ├── service_spec.rb
│ ├── user_spec.rb
│ ├── webhook_spec.rb
│ ├── webhook_subscription_spec.rb
│ └── whitelisted_jwt_spec.rb
└── requests
│ ├── api
│ └── v1
│ │ └── accounts_spec.rb
│ └── authentication_spec.rb
├── storage
└── .keep
├── test
├── application_system_test_case.rb
├── channels
│ └── application_cable
│ │ └── connection_test.rb
├── controllers
│ └── .keep
├── fixtures
│ └── files
│ │ └── .keep
├── helpers
│ └── .keep
├── integration
│ └── .keep
├── mailers
│ └── .keep
├── models
│ └── .keep
├── system
│ └── .keep
└── test_helper.rb
├── tmp
└── .keep
└── vendor
├── .keep
└── javascript
└── .keep
/.env.dev:
--------------------------------------------------------------------------------
1 | RAILS_ENV=production
2 | APP_ENV=development
3 | REDIS_URL=redis://localhost:6379/0
4 | SIDEKIQ_USERNAME=nohooks
5 | SIDEKIQ_PASSWORD=nohooks
6 | DATABASE_URL=postgres://convoy:convoy@localhost:5432/nohooksdb
7 | DATABASE_TEST_URL=postgres://postgres:postgres@localhost:5432/nohooksdb_test
8 | GOOGLE_CLIENT_ID="11122233344455-39dfsdfsdfvult7dsgfsdfsd6t4cdsfsdsdf.apps.googleusercontent.com"
9 | GOOGLE_CLIENT_SECRET="BBBBB-AAAAA_fdsafnsdkjhknkkjdhsf"
10 | GOOGLE_SITE_URL='https://oauth2.googleapis.com'
11 | GOOGLE_AUTHORIZE_URL='https://accounts.google.com/o/oauth2/auth'
12 | GOOGLE_TOKEN_URL='/token'
13 | GOOGLE_REDIRECT_URI='https://app.nohooks.io/auth'
14 | X_GOOGLE_API='https://www.googleapis.com/oauth2/v3/tokeninfo?access_token='
15 | GOOGLE_API='https://www.googleapis.com/oauth2/v2/userinfo?access_token='
16 | JWT_SECRET_KEY=7283ac7d35487072f8926ddea072f4acf6f8403ab1038124b92da7e74aa8
17 | JWT_DEFAULT_EXPIRY_TIME=60
18 | JWT_LONG_EXPIRY_TIME=600
19 | POLL_INTERVAL=10
20 | PUSH_WEBHOOKS_INTERVAL=15
21 | CONVOY_API_KEY=CO.4TXkOkfXymlFNDKl.kjhskjfhkjsdhfsudihfudskufjhdsjhjfiushuknhfjksdhjkfhs
22 | CONVOY_PROJECT_ID=SUHFUISHFUDSIHFKHSDKUFHSDFUISD
23 | NOTION_OAUTH_CLIENT_ID=3453242342-1111-2222-3333-7add32d3d32d238536
24 | NOTION_OAUTH_CLIENT_SECRET=secret_onhgvhjgjhbjygjhfghjgjhghj5bKAa324ISQkpF
25 | NOTION_SITE_URL=https://api.notion.com
26 | NOTION_TOKEN_URL=/v1/oauth/token
27 | NOTION_REDIRECT_URI=https://app.nohooks.io/
28 |
--------------------------------------------------------------------------------
/.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 any vendored files as having been vendored.
7 | vendor/* linguist-vendored
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.rbc
2 | capybara-*.html
3 | .rspec
4 | /db/*.sqlite3
5 | /db/*.sqlite3-journal
6 | /db/*.sqlite3-[0-9]*
7 | /public/system
8 | /coverage/
9 | /spec/tmp
10 | *.orig
11 | rerun.txt
12 | pickle-email-*.html
13 |
14 | # Ignore all logfiles and tempfiles.
15 | /log/*
16 | /tmp/*
17 | !/log/.keep
18 | !/tmp/.keep
19 |
20 | # TODO Comment out this rule if you are OK with secrets being uploaded to the repo
21 | config/initializers/secret_token.rb
22 | config/master.key
23 |
24 | # Only include if you have production secrets in this file, which is no longer a Rails default
25 | # config/secrets.yml
26 |
27 | # dotenv, dotenv-rails
28 | # TODO Comment out these rules if environment variables can be committed
29 | .env
30 | .env*.local
31 |
32 | ## Environment normalization:
33 | /.bundle
34 | /vendor/bundle
35 |
36 | # these should all be checked in to normalize the environment:
37 | # Gemfile.lock, .ruby-version, .ruby-gemset
38 |
39 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
40 | .rvmrc
41 |
42 | # if using bower-rails ignore default bower_components path bower.json files
43 | /vendor/assets/bower_components
44 | *.bowerrc
45 | bower.json
46 |
47 | # Ignore pow environment settings
48 | .powenv
49 |
50 | # Ignore Byebug command history file.
51 | .byebug_history
52 |
53 | # Ignore node_modules
54 | node_modules/
55 |
56 | # Ignore precompiled javascript packs
57 | /public/packs
58 | /public/packs-test
59 | /public/assets
60 |
61 | # Ignore yarn files
62 | /yarn-error.log
63 | yarn-debug.log*
64 | .yarn-integrity
65 |
66 | # Ignore uploaded files in development
67 | /storage/*
68 | !/storage/.keep
69 | /public/uploads
70 |
71 | # Ignore master key for decrypting credentials and more.
72 | /config/master.key
73 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 3.2.2
2 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ruby:3.2.2-alpine as build
2 |
3 | WORKDIR /app
4 |
5 | COPY ./Gemfile /app/Gemfile
6 | COPY ./Gemfile.lock /app/Gemfile.lock
7 |
8 | RUN apk add --no-cache \
9 | git \
10 | bash \
11 | build-base \
12 | libxml2-dev \
13 | libxslt-dev \
14 | nodejs \
15 | tzdata \
16 | openssl \
17 | postgresql-dev
18 |
19 | ENV BUNDLER_VERSION='2.3.26'
20 | RUN gem install bundler --no-document -v '2.3.26'
21 |
22 | RUN bundle config build.nokogiri --use-system-libraries &&\
23 | bundle install --jobs=3 --retry=3 --without development test
24 |
25 | FROM ruby:3.2.2-alpine
26 |
27 | WORKDIR /app
28 |
29 | COPY . /app
30 |
31 | RUN apk add --no-cache \
32 | bash \
33 | postgresql-dev \
34 | tzdata \
35 | libc6-compat
36 |
37 | COPY --from=build /usr/local/bundle/ /usr/local/bundle
38 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3 |
4 | ruby "3.2.2"
5 |
6 | # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
7 | gem "rails", "~> 7.0.4", ">= 7.0.4.3"
8 |
9 | # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
10 | gem "sprockets-rails"
11 |
12 | gem 'dotenv-rails'
13 | gem 'pg', '>= 0.18', '< 2.0'
14 |
15 | # Use sqlite3 as the database for Active Record
16 | gem "sqlite3", "~> 1.4"
17 |
18 | # Use the Puma web server [https://github.com/puma/puma]
19 | gem "puma", "~> 5.0"
20 |
21 | # Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
22 | gem "importmap-rails"
23 |
24 | # Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
25 | gem "turbo-rails"
26 |
27 | # Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
28 | gem "stimulus-rails"
29 |
30 | # Build JSON APIs with ease [https://github.com/rails/jbuilder]
31 | gem "jbuilder"
32 |
33 | # Use Redis adapter to run Action Cable in production
34 | gem "redis", "~> 4.0"
35 |
36 | # Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
37 | # gem "kredis"
38 |
39 | # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
40 | # gem "bcrypt", "~> 3.1.7"
41 |
42 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
43 | gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ]
44 |
45 | # Reduces boot times through caching; required in config/boot.rb
46 | gem "bootsnap", require: false
47 |
48 | # Use Sass to process CSS
49 | # gem "sassc-rails"
50 |
51 | # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
52 | # gem "image_processing", "~> 1.2"
53 |
54 | # HTTP Things
55 | gem 'httparty'
56 |
57 | # Interactors
58 | gem 'interactor', '~> 3.0'
59 |
60 | # Sidekiq for background processing
61 | gem 'sidekiq'
62 |
63 | # For periodic cron jobs
64 | gem 'clockwork'
65 |
66 | # For ActiveRecord Datetime filtering
67 | gem 'by_star', git: 'https://github.com/radar/by_star'
68 |
69 | # Use Rack CORS
70 | gem 'rack-cors', require: 'rack/cors'
71 |
72 | # Sentry
73 | gem 'sentry-ruby'
74 | gem 'sentry-rails'
75 | gem 'sentry-sidekiq'
76 |
77 | # Batching
78 | gem 'batch-loader'
79 |
80 | # API Serialization
81 | gem 'active_model_serializers', '~> 0.10.0'
82 |
83 | # Connection Pool
84 | gem 'connection_pool'
85 |
86 | # Render Client
87 | gem 'render_ruby'
88 | gem 'faraday'
89 |
90 | # Notion Client
91 | gem 'notion-ruby-client'
92 |
93 | # Digital Ocean Client
94 | gem 'droplet_kit'
95 |
96 | # Google PubSub
97 | # gem 'google-cloud-pubsub'
98 | # gem 'grpc'
99 | gem 'convoy.rb'
100 |
101 | # User management
102 | gem 'devise'
103 | gem 'omniauth'
104 | gem 'omniauth-google-oauth2'
105 | gem "omniauth-rails_csrf_protection"
106 |
107 | group :development, :test do
108 | # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
109 | gem "debug", platforms: %i[ mri mingw x64_mingw ]
110 | gem "database_cleaner"
111 | gem "factory_bot_rails"
112 | gem "pry"
113 | gem "rspec-rails"
114 | gem "rspec_junit_formatter"
115 | end
116 |
117 | group :development do
118 | # Use console on exceptions pages [https://github.com/rails/web-console]
119 | gem "web-console"
120 |
121 | # Annotation
122 | gem 'annotate'
123 |
124 | # Automatically run my tests. :)
125 | gem 'guard'
126 | gem 'guard-rspec', require: false
127 | end
128 |
129 | group :test do
130 | # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
131 | gem "capybara"
132 | gem "selenium-webdriver"
133 | gem "webdrivers"
134 |
135 | gem 'shoulda'
136 | gem 'faker'
137 | end
138 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GIT
2 | remote: https://github.com/radar/by_star
3 | revision: e1222703ec18c4db00970672e75faa0c4ee7d7d9
4 | specs:
5 | by_star (4.0.1)
6 | activesupport (>= 3.2.0)
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | actioncable (7.0.5)
12 | actionpack (= 7.0.5)
13 | activesupport (= 7.0.5)
14 | nio4r (~> 2.0)
15 | websocket-driver (>= 0.6.1)
16 | actionmailbox (7.0.5)
17 | actionpack (= 7.0.5)
18 | activejob (= 7.0.5)
19 | activerecord (= 7.0.5)
20 | activestorage (= 7.0.5)
21 | activesupport (= 7.0.5)
22 | mail (>= 2.7.1)
23 | net-imap
24 | net-pop
25 | net-smtp
26 | actionmailer (7.0.5)
27 | actionpack (= 7.0.5)
28 | actionview (= 7.0.5)
29 | activejob (= 7.0.5)
30 | activesupport (= 7.0.5)
31 | mail (~> 2.5, >= 2.5.4)
32 | net-imap
33 | net-pop
34 | net-smtp
35 | rails-dom-testing (~> 2.0)
36 | actionpack (7.0.5)
37 | actionview (= 7.0.5)
38 | activesupport (= 7.0.5)
39 | rack (~> 2.0, >= 2.2.4)
40 | rack-test (>= 0.6.3)
41 | rails-dom-testing (~> 2.0)
42 | rails-html-sanitizer (~> 1.0, >= 1.2.0)
43 | actiontext (7.0.5)
44 | actionpack (= 7.0.5)
45 | activerecord (= 7.0.5)
46 | activestorage (= 7.0.5)
47 | activesupport (= 7.0.5)
48 | globalid (>= 0.6.0)
49 | nokogiri (>= 1.8.5)
50 | actionview (7.0.5)
51 | activesupport (= 7.0.5)
52 | builder (~> 3.1)
53 | erubi (~> 1.4)
54 | rails-dom-testing (~> 2.0)
55 | rails-html-sanitizer (~> 1.1, >= 1.2.0)
56 | active_model_serializers (0.10.13)
57 | actionpack (>= 4.1, < 7.1)
58 | activemodel (>= 4.1, < 7.1)
59 | case_transform (>= 0.2)
60 | jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
61 | activejob (7.0.5)
62 | activesupport (= 7.0.5)
63 | globalid (>= 0.3.6)
64 | activemodel (7.0.5)
65 | activesupport (= 7.0.5)
66 | activerecord (7.0.5)
67 | activemodel (= 7.0.5)
68 | activesupport (= 7.0.5)
69 | activestorage (7.0.5)
70 | actionpack (= 7.0.5)
71 | activejob (= 7.0.5)
72 | activerecord (= 7.0.5)
73 | activesupport (= 7.0.5)
74 | marcel (~> 1.0)
75 | mini_mime (>= 1.1.0)
76 | activesupport (7.0.5)
77 | concurrent-ruby (~> 1.0, >= 1.0.2)
78 | i18n (>= 1.6, < 2)
79 | minitest (>= 5.1)
80 | tzinfo (~> 2.0)
81 | addressable (2.8.4)
82 | public_suffix (>= 2.0.2, < 6.0)
83 | annotate (3.2.0)
84 | activerecord (>= 3.2, < 8.0)
85 | rake (>= 10.4, < 14.0)
86 | axiom-types (0.1.1)
87 | descendants_tracker (~> 0.0.4)
88 | ice_nine (~> 0.11.0)
89 | thread_safe (~> 0.3, >= 0.3.1)
90 | batch-loader (2.0.1)
91 | bcrypt (3.1.18)
92 | bindex (0.8.1)
93 | bootsnap (1.16.0)
94 | msgpack (~> 1.2)
95 | builder (3.2.4)
96 | capybara (3.39.2)
97 | addressable
98 | matrix
99 | mini_mime (>= 0.1.3)
100 | nokogiri (~> 1.8)
101 | rack (>= 1.6.0)
102 | rack-test (>= 0.6.3)
103 | regexp_parser (>= 1.5, < 3.0)
104 | xpath (~> 3.2)
105 | case_transform (0.2)
106 | activesupport
107 | clockwork (3.0.2)
108 | activesupport
109 | tzinfo
110 | coderay (1.1.3)
111 | coercible (1.0.0)
112 | descendants_tracker (~> 0.0.1)
113 | concurrent-ruby (1.2.2)
114 | connection_pool (2.4.1)
115 | convoy.rb (0.3.0)
116 | zeitwerk (~> 2.5)
117 | crass (1.0.6)
118 | database_cleaner (2.0.2)
119 | database_cleaner-active_record (>= 2, < 3)
120 | database_cleaner-active_record (2.1.0)
121 | activerecord (>= 5.a)
122 | database_cleaner-core (~> 2.0.0)
123 | database_cleaner-core (2.0.1)
124 | date (3.3.3)
125 | debug (1.8.0)
126 | irb (>= 1.5.0)
127 | reline (>= 0.3.1)
128 | descendants_tracker (0.0.4)
129 | thread_safe (~> 0.3, >= 0.3.1)
130 | devise (4.9.2)
131 | bcrypt (~> 3.0)
132 | orm_adapter (~> 0.1)
133 | railties (>= 4.1.0)
134 | responders
135 | warden (~> 1.2.3)
136 | diff-lcs (1.5.0)
137 | dotenv (2.8.1)
138 | dotenv-rails (2.8.1)
139 | dotenv (= 2.8.1)
140 | railties (>= 3.2)
141 | droplet_kit (3.19.0)
142 | faraday (>= 0.15)
143 | kartograph (~> 0.2.8)
144 | resource_kit (~> 0.1.5)
145 | virtus (>= 1.0.3, <= 3)
146 | erubi (1.12.0)
147 | factory_bot (6.2.1)
148 | activesupport (>= 5.0.0)
149 | factory_bot_rails (6.2.0)
150 | factory_bot (~> 6.2.0)
151 | railties (>= 5.0.0)
152 | faker (3.2.0)
153 | i18n (>= 1.8.11, < 2)
154 | faraday (2.7.7)
155 | faraday-net_http (>= 2.0, < 3.1)
156 | ruby2_keywords (>= 0.0.4)
157 | faraday-mashify (0.1.1)
158 | faraday (~> 2.0)
159 | hashie
160 | faraday-multipart (1.0.4)
161 | multipart-post (~> 2)
162 | faraday-net_http (3.0.2)
163 | ffi (1.15.5)
164 | formatador (1.1.0)
165 | globalid (1.1.0)
166 | activesupport (>= 5.0)
167 | guard (2.18.0)
168 | formatador (>= 0.2.4)
169 | listen (>= 2.7, < 4.0)
170 | lumberjack (>= 1.0.12, < 2.0)
171 | nenv (~> 0.1)
172 | notiffany (~> 0.0)
173 | pry (>= 0.13.0)
174 | shellany (~> 0.0)
175 | thor (>= 0.18.1)
176 | guard-compat (1.2.1)
177 | guard-rspec (4.7.3)
178 | guard (~> 2.1)
179 | guard-compat (~> 1.1)
180 | rspec (>= 2.99.0, < 4.0)
181 | hashie (5.0.0)
182 | httparty (0.21.0)
183 | mini_mime (>= 1.0.0)
184 | multi_xml (>= 0.5.2)
185 | i18n (1.14.1)
186 | concurrent-ruby (~> 1.0)
187 | ice_nine (0.11.2)
188 | importmap-rails (1.2.1)
189 | actionpack (>= 6.0.0)
190 | railties (>= 6.0.0)
191 | interactor (3.1.2)
192 | io-console (0.6.0)
193 | irb (1.7.0)
194 | reline (>= 0.3.0)
195 | jbuilder (2.11.5)
196 | actionview (>= 5.0.0)
197 | activesupport (>= 5.0.0)
198 | jsonapi-renderer (0.2.2)
199 | jwt (2.7.1)
200 | kartograph (0.2.8)
201 | listen (3.8.0)
202 | rb-fsevent (~> 0.10, >= 0.10.3)
203 | rb-inotify (~> 0.9, >= 0.9.10)
204 | loofah (2.21.3)
205 | crass (~> 1.0.2)
206 | nokogiri (>= 1.12.0)
207 | lumberjack (1.2.8)
208 | mail (2.8.1)
209 | mini_mime (>= 0.1.1)
210 | net-imap
211 | net-pop
212 | net-smtp
213 | marcel (1.0.2)
214 | matrix (0.4.2)
215 | method_source (1.0.0)
216 | mini_mime (1.1.2)
217 | minitest (5.18.1)
218 | msgpack (1.7.1)
219 | multi_xml (0.6.0)
220 | multipart-post (2.3.0)
221 | nenv (0.3.0)
222 | net-imap (0.3.6)
223 | date
224 | net-protocol
225 | net-pop (0.1.2)
226 | net-protocol
227 | net-protocol (0.2.1)
228 | timeout
229 | net-smtp (0.3.3)
230 | net-protocol
231 | nio4r (2.5.9)
232 | nokogiri (1.15.2-arm64-darwin)
233 | racc (~> 1.4)
234 | notiffany (0.1.3)
235 | nenv (~> 0.1)
236 | shellany (~> 0.0)
237 | notion-ruby-client (1.2.1)
238 | dotenv
239 | faraday (>= 2.0)
240 | faraday-mashify (>= 0.1.1)
241 | faraday-multipart (>= 1.0.4)
242 | hashie (~> 5)
243 | oauth2 (2.0.9)
244 | faraday (>= 0.17.3, < 3.0)
245 | jwt (>= 1.0, < 3.0)
246 | multi_xml (~> 0.5)
247 | rack (>= 1.2, < 4)
248 | snaky_hash (~> 2.0)
249 | version_gem (~> 1.1)
250 | omniauth (2.1.1)
251 | hashie (>= 3.4.6)
252 | rack (>= 2.2.3)
253 | rack-protection
254 | omniauth-google-oauth2 (1.1.1)
255 | jwt (>= 2.0)
256 | oauth2 (~> 2.0.6)
257 | omniauth (~> 2.0)
258 | omniauth-oauth2 (~> 1.8.0)
259 | omniauth-oauth2 (1.8.0)
260 | oauth2 (>= 1.4, < 3)
261 | omniauth (~> 2.0)
262 | omniauth-rails_csrf_protection (1.0.1)
263 | actionpack (>= 4.2)
264 | omniauth (~> 2.0)
265 | orm_adapter (0.5.0)
266 | pg (1.5.3)
267 | pry (0.14.2)
268 | coderay (~> 1.1)
269 | method_source (~> 1.0)
270 | public_suffix (5.0.1)
271 | puma (5.6.6)
272 | nio4r (~> 2.0)
273 | racc (1.7.1)
274 | rack (2.2.7)
275 | rack-cors (2.0.1)
276 | rack (>= 2.0.0)
277 | rack-protection (3.0.6)
278 | rack
279 | rack-test (2.1.0)
280 | rack (>= 1.3)
281 | rails (7.0.5)
282 | actioncable (= 7.0.5)
283 | actionmailbox (= 7.0.5)
284 | actionmailer (= 7.0.5)
285 | actionpack (= 7.0.5)
286 | actiontext (= 7.0.5)
287 | actionview (= 7.0.5)
288 | activejob (= 7.0.5)
289 | activemodel (= 7.0.5)
290 | activerecord (= 7.0.5)
291 | activestorage (= 7.0.5)
292 | activesupport (= 7.0.5)
293 | bundler (>= 1.15.0)
294 | railties (= 7.0.5)
295 | rails-dom-testing (2.0.3)
296 | activesupport (>= 4.2.0)
297 | nokogiri (>= 1.6)
298 | rails-html-sanitizer (1.6.0)
299 | loofah (~> 2.21)
300 | nokogiri (~> 1.14)
301 | railties (7.0.5)
302 | actionpack (= 7.0.5)
303 | activesupport (= 7.0.5)
304 | method_source
305 | rake (>= 12.2)
306 | thor (~> 1.0)
307 | zeitwerk (~> 2.5)
308 | rake (13.0.6)
309 | rb-fsevent (0.11.2)
310 | rb-inotify (0.10.1)
311 | ffi (~> 1.0)
312 | redis (4.8.1)
313 | redis-client (0.14.1)
314 | connection_pool
315 | regexp_parser (2.8.1)
316 | reline (0.3.5)
317 | io-console (~> 0.5)
318 | render_ruby (1.1.0)
319 | resource_kit (0.1.8)
320 | addressable (>= 2.3.6, < 3.0.0)
321 | responders (3.1.0)
322 | actionpack (>= 5.2)
323 | railties (>= 5.2)
324 | rexml (3.2.5)
325 | rspec (3.12.0)
326 | rspec-core (~> 3.12.0)
327 | rspec-expectations (~> 3.12.0)
328 | rspec-mocks (~> 3.12.0)
329 | rspec-core (3.12.2)
330 | rspec-support (~> 3.12.0)
331 | rspec-expectations (3.12.3)
332 | diff-lcs (>= 1.2.0, < 2.0)
333 | rspec-support (~> 3.12.0)
334 | rspec-mocks (3.12.5)
335 | diff-lcs (>= 1.2.0, < 2.0)
336 | rspec-support (~> 3.12.0)
337 | rspec-rails (6.0.3)
338 | actionpack (>= 6.1)
339 | activesupport (>= 6.1)
340 | railties (>= 6.1)
341 | rspec-core (~> 3.12)
342 | rspec-expectations (~> 3.12)
343 | rspec-mocks (~> 3.12)
344 | rspec-support (~> 3.12)
345 | rspec-support (3.12.0)
346 | rspec_junit_formatter (0.6.0)
347 | rspec-core (>= 2, < 4, != 2.12.0)
348 | ruby2_keywords (0.0.5)
349 | rubyzip (2.3.2)
350 | selenium-webdriver (4.10.0)
351 | rexml (~> 3.2, >= 3.2.5)
352 | rubyzip (>= 1.2.2, < 3.0)
353 | websocket (~> 1.0)
354 | sentry-rails (5.9.0)
355 | railties (>= 5.0)
356 | sentry-ruby (~> 5.9.0)
357 | sentry-ruby (5.9.0)
358 | concurrent-ruby (~> 1.0, >= 1.0.2)
359 | sentry-sidekiq (5.9.0)
360 | sentry-ruby (~> 5.9.0)
361 | sidekiq (>= 3.0)
362 | shellany (0.0.1)
363 | shoulda (4.0.0)
364 | shoulda-context (~> 2.0)
365 | shoulda-matchers (~> 4.0)
366 | shoulda-context (2.0.0)
367 | shoulda-matchers (4.5.1)
368 | activesupport (>= 4.2.0)
369 | sidekiq (7.1.2)
370 | concurrent-ruby (< 2)
371 | connection_pool (>= 2.3.0)
372 | rack (>= 2.2.4)
373 | redis-client (>= 0.14.0)
374 | snaky_hash (2.0.1)
375 | hashie
376 | version_gem (~> 1.1, >= 1.1.1)
377 | sprockets (4.2.0)
378 | concurrent-ruby (~> 1.0)
379 | rack (>= 2.2.4, < 4)
380 | sprockets-rails (3.4.2)
381 | actionpack (>= 5.2)
382 | activesupport (>= 5.2)
383 | sprockets (>= 3.0.0)
384 | sqlite3 (1.6.3-arm64-darwin)
385 | stimulus-rails (1.2.1)
386 | railties (>= 6.0.0)
387 | thor (1.2.2)
388 | thread_safe (0.3.6)
389 | timeout (0.3.2)
390 | turbo-rails (1.4.0)
391 | actionpack (>= 6.0.0)
392 | activejob (>= 6.0.0)
393 | railties (>= 6.0.0)
394 | tzinfo (2.0.6)
395 | concurrent-ruby (~> 1.0)
396 | version_gem (1.1.3)
397 | virtus (2.0.0)
398 | axiom-types (~> 0.1)
399 | coercible (~> 1.0)
400 | descendants_tracker (~> 0.0, >= 0.0.3)
401 | warden (1.2.9)
402 | rack (>= 2.0.9)
403 | web-console (4.2.0)
404 | actionview (>= 6.0.0)
405 | activemodel (>= 6.0.0)
406 | bindex (>= 0.4.0)
407 | railties (>= 6.0.0)
408 | webdrivers (5.2.0)
409 | nokogiri (~> 1.6)
410 | rubyzip (>= 1.3.0)
411 | selenium-webdriver (~> 4.0)
412 | websocket (1.2.9)
413 | websocket-driver (0.7.5)
414 | websocket-extensions (>= 0.1.0)
415 | websocket-extensions (0.1.5)
416 | xpath (3.2.0)
417 | nokogiri (~> 1.8)
418 | zeitwerk (2.6.8)
419 |
420 | PLATFORMS
421 | arm64-darwin-22
422 |
423 | DEPENDENCIES
424 | active_model_serializers (~> 0.10.0)
425 | annotate
426 | batch-loader
427 | bootsnap
428 | by_star!
429 | capybara
430 | clockwork
431 | connection_pool
432 | convoy.rb
433 | database_cleaner
434 | debug
435 | devise
436 | dotenv-rails
437 | droplet_kit
438 | factory_bot_rails
439 | faker
440 | faraday
441 | guard
442 | guard-rspec
443 | httparty
444 | importmap-rails
445 | interactor (~> 3.0)
446 | jbuilder
447 | notion-ruby-client
448 | omniauth
449 | omniauth-google-oauth2
450 | omniauth-rails_csrf_protection
451 | pg (>= 0.18, < 2.0)
452 | pry
453 | puma (~> 5.0)
454 | rack-cors
455 | rails (~> 7.0.4, >= 7.0.4.3)
456 | redis (~> 4.0)
457 | render_ruby
458 | rspec-rails
459 | rspec_junit_formatter
460 | selenium-webdriver
461 | sentry-rails
462 | sentry-ruby
463 | sentry-sidekiq
464 | shoulda
465 | sidekiq
466 | sprockets-rails
467 | sqlite3 (~> 1.4)
468 | stimulus-rails
469 | turbo-rails
470 | tzinfo-data
471 | web-console
472 | webdrivers
473 |
474 | RUBY VERSION
475 | ruby 3.2.2p53
476 |
477 | BUNDLED WITH
478 | 2.4.10
479 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Frain Technologies
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nohooks
2 | Nohooks is a project to add webhooks to popular web services using [Convoy](https://github.com/frain-dev/convoy). The service is also hosted at [app.nohooks.io](https://app.nohooks.io)
3 |
4 | ## Requirements
5 | - Postgres
6 | - Redis
7 | - Convoy
8 |
9 | ## License
10 | [MIT](https://github.com/frain-dev/nohooks-backend/blob/main/LICENSE)
11 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative "config/application"
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../stylesheets .css
3 | //= link_tree ../../javascript .js
4 | //= link_tree ../../../vendor/javascript .js
5 |
--------------------------------------------------------------------------------
/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/app/assets/images/.keep
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's
6 | * vendor/assets/stylesheets directory can be referenced here using a relative path.
7 | *
8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/controllers/api/v1/accounts_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::V1::AccountsController < ApplicationController
2 | before_action :set_account, only: %i[show update destroy]
3 | before_action :set_account_type, only: %i[create update]
4 |
5 | def index
6 | accounts = current_user.accounts.order('created_at DESC')
7 | json = generate_json(status: true,
8 | message: ApiResponse::Account.retrieved_successfully,
9 | data: accounts)
10 | render status: 200, json: json
11 | end
12 |
13 | def show
14 | json = generate_json(status: true,
15 | message: ApiResponse::Account.retrieved_successfully,
16 | data: @account)
17 | render status: 200, json: json
18 | end
19 |
20 | def create
21 | type = create_params[:type]
22 |
23 | if type == "render"
24 | create_render_account_configuration
25 | elsif type == "notion"
26 | create_notion_account_configuration
27 | elsif type == "digital_ocean"
28 | create_digital_ocean_configuration
29 | else
30 | json = generate_json(status: true, message: ApiResponse::Account.creation_failed)
31 | render status: 400, json: json
32 | end
33 |
34 | json = generate_json(status: true,
35 | message: ApiResponse::Account.created_successfully,
36 | data: @account)
37 | render status: 201, json: json
38 |
39 | rescue OAuth2::Error => e
40 | json = generate_json(status: false, message: e.description)
41 | render status: 400, json: json
42 | end
43 |
44 | def update
45 | if @account.configurable_type == "RenderAccountConfiguration"
46 | update_render_configuration
47 | elsif @account.configurable_type == "NotionAccountConfiguration"
48 | update_notion_configuration
49 | elsif @account.configurable_type == "DigitalOceanAccountConfiguration"
50 | update_digital_ocean_configuration
51 | else
52 | json = generate_json(status: false, message: ApiResponse::Account.update_failed)
53 | render status: 400, json: json
54 | end
55 |
56 |
57 | @account.touch
58 |
59 | json = generate_json(status: true,
60 | message: "Account updated successfully",
61 | data: @account)
62 | render status: 201, json: json
63 | end
64 |
65 | def destroy
66 | @account.destroy
67 | json = generate_json(status: true, message: "Account deleted successfully")
68 | render status: 200, json: json
69 | end
70 |
71 | private
72 |
73 | def create_render_account_configuration
74 | ActiveRecord::Base.transaction do
75 | render_account_config = RenderAccountConfiguration.create!(
76 | api_key: create_params[:data][:api_key]
77 | )
78 |
79 | @account = Account.create!(
80 | user: current_user,
81 | name: create_params[:name],
82 | sync_start_datetime: Time.now.utc.iso8601(3),
83 | configurable: render_account_config
84 | )
85 |
86 | portal_link = Convoy::PortalLink.new(
87 | data: {
88 | name: "#{@account.name}'s dashboard",
89 | owner_id: @account.id,
90 | can_manage_endpoint: true
91 | }
92 | )
93 |
94 | res = portal_link.save
95 | raise StandardError, "couldn't create portal link" if res&.response.nil? ||
96 | res&.response['status'] == false
97 |
98 | @account.update!(portal_link_url: res.response['data']['url'])
99 | end
100 | end
101 |
102 | def create_digital_ocean_configuration
103 | ActiveRecord::Base.transaction do
104 | digital_ocean_config = DigitalOceanAccountConfiguration.create!(
105 | access_token: create_params[:data][:access_token]
106 | )
107 |
108 | @account = Account.create!(
109 | user: current_user,
110 | name: create_params[:name],
111 | sync_start_datetime: Time.now.utc.iso8601(3),
112 | configurable: digital_ocean_config
113 | )
114 |
115 | portal_link = Convoy::PortalLink.new(
116 | data: {
117 | name: "#{@account.name}'s dashboard",
118 | owner_id: @account.id,
119 | can_manage_endpoint: true
120 | }
121 | )
122 |
123 | res = portal_link.save
124 | raise StandardError, "couldn't create portal link" if res&.response.nil? ||
125 | res&.response['status'] == false
126 |
127 | @account.update!(portal_link_url: res.response['data']['url'])
128 | end
129 | end
130 |
131 | def create_notion_account_configuration
132 | client = OAuth2::Client.new(ENV['NOTION_OAUTH_CLIENT_ID'],
133 | ENV['NOTION_OAUTH_CLIENT_SECRET'],
134 | site: ENV['NOTION_SITE_URL'],
135 | token_url: ENV['NOTION_TOKEN_URL'])
136 |
137 | params = {redirect_uri: ENV['NOTION_REDIRECT_URI']}
138 | params = params.merge(auth_header)
139 | access_token = client.auth_code.get_token(create_params[:data][:code], params)
140 |
141 | ActiveRecord::Base.transaction do
142 | notion_account_config = NotionAccountConfiguration.create!(
143 | access_token: access_token.token
144 | )
145 |
146 | @account = Account.create!(
147 | user: current_user,
148 | name: create_params[:name],
149 | sync_start_datetime: Time.now.utc.iso8601(3),
150 | configurable: notion_account_config
151 | )
152 |
153 | portal_link = Convoy::PortalLink.new(
154 | data: {
155 | name: "#{}'s dashboard",
156 | owner_id: @account.id,
157 | can_manage_endpoint: true
158 | }
159 | )
160 |
161 | databases = create_params[:data][:database].each do |db_id|
162 | NotionDatabase.create!(account: @account, database_id: db_id)
163 | end
164 |
165 | res = portal_link.save
166 | raise StandardError, "couldn't create portal link" if res&.response.nil? ||
167 | res&.response['status'] == false
168 |
169 | @account.update!(portal_link_url: res.response['data']['url'])
170 | end
171 | end
172 |
173 | def update_render_configuration
174 | ActiveRecord::Base.transaction do
175 | @account.update!(name: update_params[:name])
176 | if !update_params[:data].nil?
177 | @account.configurable.update!(update_params[:data])
178 | end
179 | end
180 | end
181 |
182 | def update_digital_ocean_configuration
183 | ActiveRecord::Base.transaction do
184 | @account.update!(name: update_params[:name])
185 | if !update_params[:data].nil?
186 | @account.configurable.update!(update_params[:data])
187 | end
188 | end
189 | end
190 |
191 | def update_notion_configuration
192 | ActiveRecord::Base.transaction do
193 | @account.update!(name: update_params[:name])
194 | if !update_params[:data].nil?
195 | NotionDatabase.where(account: @account).destroy_all
196 | update_params[:data][:database].each do |db_id|
197 | NotionDatabase.create!(account: @account, database_id: db_id)
198 | end
199 | end
200 | end
201 | end
202 |
203 | def set_account
204 | @account ||= Account.find_by!(user: current_user, id: params[:id])
205 | end
206 |
207 | def set_account_type
208 | @account_type ||= create_params[:type]
209 |
210 | if @account_type.nil?
211 | if @account&.configurable_type == "RenderAccountConfiguration"
212 | return "render"
213 | elsif @account&.configurable_type == "NotionAccountConfiguration"
214 | return "notion"
215 | elsif @account&.configurable_type == "DigitalOceanAccountConfiguration"
216 | return "digital_ocean"
217 | end
218 | end
219 | end
220 |
221 | def create_params
222 | params.permit(:type, :name, data: {})
223 | end
224 |
225 | def update_params
226 | params.permit(:name, data: {})
227 | end
228 |
229 | def auth_header
230 | authenticator = OAuth2::Authenticator.new(
231 | ENV['NOTION_OAUTH_CLIENT_ID'],
232 | ENV['NOTION_OAUTH_CLIENT_SECRET'],
233 | :basic_auth)
234 |
235 | authenticator.apply({})
236 | end
237 | end
238 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::API
2 | include Response
3 |
4 | rescue_from ::ActiveRecord::RecordNotFound, with: :record_not_found
5 | rescue_from ::ActiveRecord::RecordInvalid, with: :record_invalid
6 | rescue_from ::ActiveRecord::RecordNotUnique, with: :record_not_unique
7 |
8 | before_action :authorize_request
9 |
10 | attr_reader :current_user
11 |
12 | protected
13 |
14 | def record_invalid(e)
15 | json = { status: false, message: e.message, errors: e.record.errors }
16 | render status: 400, json: json
17 | end
18 |
19 | def record_not_found(e)
20 | json = generate_json(status: false, message: e.message)
21 | render status: 400, json: json
22 | end
23 |
24 | def record_not_unique(e)
25 | json = generate_json( status: false, message: 'Record exists already')
26 | render status: 400, json: json
27 | end
28 |
29 | private
30 |
31 | def authorize_request
32 | @current_user = Auth::AuthorizeApiRequest.new(request.headers).call[:user]
33 | rescue StandardError => e
34 | render status: 401, json: { status: false, message: e.message }
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/app/controllers/authentication_controller.rb:
--------------------------------------------------------------------------------
1 | class AuthenticationController < ApplicationController
2 | include HTTParty
3 |
4 | skip_before_action :authorize_request, only: :google_oauth
5 |
6 | def google_oauth
7 | google_user = get_google_user_data(google_params[:access_token])
8 |
9 | if google_user.code != 200
10 | json = generate_json(status: false,
11 | message: ApiResponse::User.invalid_access_token)
12 | render status: 400, json: json
13 | return
14 | end
15 |
16 | user = User.create_user_for_google(google_user)
17 |
18 | # Generate Access Token & respond.
19 | token = Auth::AuthenticateUser.new(user.email, true).call
20 | data = Models::RegistrationResponse.new(user: user,
21 | profile: user.profile,
22 | token: token)
23 |
24 | json = generate_json(status: true,
25 | message: ApiResponse::User.registration_successful,
26 | data: data)
27 |
28 | render status: 200, json: json
29 | end
30 |
31 | private
32 |
33 |
34 | def get_google_user_data(access_token)
35 | url = "#{ENV['GOOGLE_API']}#{access_token}"
36 | HTTParty.get(url)
37 | end
38 |
39 | def google_params
40 | params.permit(:access_token)
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/app/controllers/concerns/response.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Response
4 | class SerializationError < StandardError
5 | end
6 |
7 | def generate_json(status: true, message: 'success', data: {})
8 | data = Models::BaseModel.new(status: status, message: message, data: data)
9 | ActiveModelSerializers::SerializableResource.new(data).as_json
10 | rescue StandardError => e
11 | raise SerializationError, "response could not be serialized: #{e.message}"
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/helpers/api/v1/accounts_helper.rb:
--------------------------------------------------------------------------------
1 | module Api::V1::AccountsHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/authentication_helper.rb:
--------------------------------------------------------------------------------
1 | module AuthenticationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/interactors/application_interactor.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationInteractor
4 | include Interactor
5 |
6 | def fail_context_to_sentry!(err_object)
7 | Sentry.capture_exception(err_object[:error])
8 | end
9 |
10 | def fail_context!(err_object)
11 | context.fail!(error: err_object)
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/interactors/digital_ocean/poll_droplets.rb:
--------------------------------------------------------------------------------
1 | module DigitalOcean
2 | class PollDroplets < ApplicationInteractor
3 | def call
4 | @account = context.account
5 | @client = DropletKit::Client.new(access_token: @account.configurable.access_token)
6 |
7 | return if @account.configurable_type != "DigitalOceanAccountConfiguration"
8 |
9 | droplets = retrieve_account_droplets
10 |
11 | droplets.each do |droplet|
12 | computed_hash = compute_droplet_hash(droplet)
13 |
14 | db_droplet = DigitalOceanDroplet.find_by_droplet_id(droplet.id)
15 | if db_droplet.nil?
16 | send_droplet_created_event(droplet, computed_hash)
17 | next
18 | end
19 |
20 | if computed_hash != db_droplet.object_hash
21 | send_droplet_updated_event(db_droplet, droplet, computed_hash)
22 | end
23 | end
24 |
25 | deleted_droplets = retrieve_deleted_droplets(droplets)
26 | return if deleted_droplets.empty?
27 |
28 | deleted_droplets.each do |db_droplet|
29 | send_droplet_deleted_event(db_droplet)
30 | end
31 |
32 | rescue StandardError => e
33 | fail_context_to_sentry!(error: e)
34 | end
35 |
36 | private
37 |
38 | def retrieve_account_droplets
39 | @client.droplets.all
40 | end
41 |
42 | def retrieve_deleted_droplets(droplets)
43 | account_droplets = DigitalOceanDroplet.where(account: @account).pluck(:droplet_id)
44 | digital_ocean_droplets = retrieve_account_droplets.map { |d| d.id.to_s }
45 | deleted_droplets = account_droplets - digital_ocean_droplets
46 | DigitalOceanDroplet.where(droplet_id: deleted_droplets)
47 | end
48 |
49 | def compute_droplet_hash(droplet)
50 | Digest::SHA256.hexdigest(droplet.to_h.to_json)
51 | end
52 |
53 | def send_droplet_created_event(droplet, hash)
54 | ActiveRecord::Base.transaction do
55 | DigitalOceanDroplet.create!(account: @account,
56 | droplet_id: droplet.id, object_hash: hash)
57 | Webhook.create!(account: @account,
58 | event_type: "droplet.created", payload: droplet.to_h)
59 | end
60 | end
61 |
62 | def send_droplet_updated_event(db_droplet, droplet, hash)
63 | case droplet.status
64 | when "off"
65 | event_type = "droplet.off"
66 | when "archive"
67 | event_type = "droplet.archived"
68 | when "active"
69 | event_type = "droplet.updated"
70 | end
71 |
72 | ActiveRecord::Base.transaction do
73 | update = db_droplet.update!(object_hash: hash)
74 | Webhook.create!(account: @account, event_type: event_type, payload: droplet.to_h)
75 | end
76 | end
77 |
78 | def send_droplet_deleted_event(db_droplet)
79 | payload = {
80 | id: db_droplet.droplet_id
81 | }
82 |
83 | ActiveRecord::Base.transaction do
84 | Webhook.create!(account: @account, event_type: "droplet.deleted", payload: payload)
85 | db_droplet.destroy!
86 | end
87 | end
88 | end
89 | end
90 |
--------------------------------------------------------------------------------
/app/interactors/notion/poll_databases.rb:
--------------------------------------------------------------------------------
1 | module Notion
2 | class PollDatabases < ApplicationInteractor
3 | def call
4 | @account = context.account
5 | @client = Notion::Client.new(token: @account.configurable.access_token)
6 | @databases = @account.notion_databases
7 |
8 | return if @account.configurable_type != "NotionAccountConfiguration"
9 |
10 | @databases.each do |database|
11 | rows = retrieve_rows(database)
12 | next if rows.nil? || rows.empty?
13 |
14 | rows.each do |row|
15 | computed_hash = compute_row_hash(row)
16 | db_row = NotionRow.find_by_row_id(row.id)
17 |
18 | if db_row.nil?
19 | send_row_created_event(database, row, computed_hash)
20 | next
21 | end
22 |
23 | if computed_hash != db_row.object_hash
24 | send_row_updated_event(db_row, row, computed_hash)
25 | end
26 | end
27 |
28 | deleted_rows = retrieve_deleted_rows(database, rows)
29 | next if deleted_rows.empty?
30 |
31 | deleted_rows.each do |db_row|
32 | send_row_deleted_event(db_row)
33 | end
34 | end
35 |
36 | rescue StandardError => e
37 | fail_context_to_sentry!(error: e)
38 | end
39 |
40 | private
41 |
42 | def retrieve_rows(database, cursor: nil)
43 | options = {database_id: database.database_id, page_size: 100}
44 | unless cursor.nil?
45 | options[:start_cursor] = cursor
46 | end
47 |
48 | begin
49 | rows = @client.database_query(options)
50 | rescue Notion::Api::Errors::ObjectNotFound
51 | database.destroy
52 | return
53 | end
54 |
55 | unless rows.has_more
56 | return rows.results
57 | end
58 |
59 | return rows.results.concat(retrieve_rows(database, cursor: rows.next_cursor))
60 | end
61 |
62 | def retrieve_deleted_rows(database, rows)
63 | account_rows = NotionRow.where(notion_database: database).pluck(:row_id)
64 | notion_rows = rows.map {|r| r.id }
65 | deleted_rows = account_rows - notion_rows
66 | NotionRow.where(row_id: deleted_rows)
67 | end
68 |
69 | def compute_row_hash(row)
70 | Digest::SHA256.hexdigest(row.to_h.to_json)
71 | end
72 |
73 | def send_row_created_event(database, row, hash)
74 | ActiveRecord::Base.transaction do
75 | NotionRow.create!(notion_database: database,
76 | row_id: row.id, object_hash: hash)
77 | Webhook.create!(account: @account,
78 | event_type: "database.row.created", payload: row.to_h)
79 | end
80 | end
81 |
82 | def send_row_updated_event(db_row, row, hash)
83 | ActiveRecord::Base.transaction do
84 | update = db_row.update!(object_hash: hash)
85 | Webhook.create!(account: @account, event_type: "database.row.updated",
86 | payload: row.to_h)
87 | end
88 | end
89 |
90 | def send_row_deleted_event(db_row)
91 | payload = {
92 | id: db_row.row_id
93 | }
94 |
95 | ActiveRecord::Base.transaction do
96 | Webhook.create!(account: @account, event_type: "database.row.deleted",
97 | payload: payload)
98 | db_row.destroy!
99 | end
100 | end
101 | end
102 | end
103 |
--------------------------------------------------------------------------------
/app/interactors/render/poll_deployments.rb:
--------------------------------------------------------------------------------
1 | module Render
2 | class PollDeployments < ApplicationInteractor
3 | def call
4 | @account = context.account
5 | @client = RenderRuby::Client.new(api_key: @account.configurable.api_key)
6 | @services = @account.render_services
7 |
8 | @services.each do |service|
9 | deployments = retrieve_account_deployments(service)
10 | next if deployments.empty?
11 |
12 | deployments.each do |deployment|
13 | deployment = deployment.deploy
14 | computed_hash = compute_deployment_hash(deployment)
15 | db_deployment = RenderDeployment.find_by_deployment_id(deployment.id)
16 |
17 | if db_deployment.nil?
18 | send_deployment_created_event(service, deployment, computed_hash)
19 | next
20 | end
21 |
22 | if computed_hash != db_deployment.object_hash
23 | send_deployment_status_event(db_deployment, deployment, computed_hash)
24 | end
25 | end
26 | end
27 |
28 | rescue StandardError => e
29 | fail_context_to_sentry!(error: e)
30 | end
31 |
32 | private
33 |
34 | def retrieve_account_deployments(service, cursor: nil)
35 | created_after = @account.sync_start_datetime.utc.iso8601(3)
36 | deployments = @client.deploys.list(service_id: service.service_id,
37 | limit: 100, cursor: cursor,
38 | createdAfter: created_after)
39 | if deployments.total == 0
40 | return deployments.data
41 | end
42 |
43 | return deployments.data.concat(
44 | retrieve_account_deployments(service, cursor: deployments.next_cursor))
45 | end
46 |
47 | def compute_deployment_hash(deployment)
48 | Digest::SHA256.hexdigest(deployment.to_h.to_json)
49 | end
50 |
51 | def send_deployment_created_event(render_service, deployment, hash)
52 | ActiveRecord::Base.transaction do
53 | RenderDeployment.create!(render_service: render_service,
54 | deployment_id: deployment.id, object_hash: hash)
55 | Webhook.create!(account: @account,
56 | event_type: "deployment.created", payload: deployment.to_h)
57 | end
58 | end
59 |
60 | def send_deployment_status_event(db_deployment, deployment, hash)
61 | case deployment.status
62 | when "live"
63 | event_type = "deployment.live"
64 | when "canceled"
65 | event_type = "deployment.canceled"
66 | when "deactivated"
67 | event_type = "deployment.deactivated"
68 | when "build_failed", "update_failed"
69 | event_type = "deployment.failed"
70 | else
71 | event_type = "deployment.updated"
72 | end
73 |
74 | ActiveRecord::Base.transaction do
75 | update = db_deployment.update!(object_hash: hash)
76 | Webhook.create!(account: @account,
77 | event_type: event_type, payload: deployment.to_h)
78 | end
79 | end
80 | end
81 | end
82 |
--------------------------------------------------------------------------------
/app/interactors/render/poll_jobs.rb:
--------------------------------------------------------------------------------
1 | module Render
2 | class PollJobs < ApplicationInteractor
3 | def call
4 | @account = context.account
5 | @client = RenderRuby::Client.new(api_key: @account.configurable.api_key)
6 | @services = @account.render_services
7 |
8 | @services.each do |service|
9 | jobs = retrieve_account_jobs(service)
10 | next if jobs.empty?
11 |
12 | jobs.each do |job|
13 | job = job.job
14 | computed_hash = compute_job_hash(job)
15 | db_job = RenderJob.find_by_job_id(job.id)
16 |
17 | if db_job.nil?
18 | send_job_created_event(service, job, computed_hash)
19 | next
20 | end
21 |
22 | if computed_hash != db_job.object_hash
23 | send_job_status_event(db_job, job, computed_hash)
24 | end
25 | end
26 | end
27 |
28 | rescue StandardError => e
29 | fail_context_to_sentry!(error: e)
30 | end
31 |
32 | private
33 |
34 | def retrieve_account_jobs(service, cursor: nil)
35 | jobs = @client.jobs.list(service_id: service.service_id,
36 | limit: 100, cursor: cursor)
37 | if jobs.total == 0
38 | return jobs.data
39 | end
40 |
41 | return jobs.data.concat(retrieve_account_jobs(service, jobs.next_cursor))
42 | end
43 |
44 | def compute_job_hash(job)
45 | Digest::SHA256.hexdigest(job.to_h.to_json)
46 | end
47 |
48 | def send_job_created_event(render_service, job, hash)
49 | ActiveRecord::Base.transaction do
50 | RenderJob.create!(render_service: render_service,
51 | job_id: job.id, object_hash: hash)
52 | Webhook.create!(account: @account,
53 | event_type: "job.created", payload: job.to_h)
54 | end
55 | end
56 |
57 | def send_job_status_event(db_job, job, hash)
58 | ActiveRecord::Base.transaction do
59 | update = db_job.update!(object_hash: hash)
60 | Webhook.create!(account: @account,
61 | event_type: "job.updated", payload: job.to_h)
62 | end
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/app/interactors/render/poll_services.rb:
--------------------------------------------------------------------------------
1 | module Render
2 | class PollServices < ApplicationInteractor
3 | def call
4 | @account = context.account
5 | @client = RenderRuby::Client.new(api_key: @account.configurable.api_key)
6 |
7 | return if @account.configurable_type != "RenderAccountConfiguration"
8 |
9 | services = retrieve_account_services
10 | return if services.empty?
11 |
12 | services.each do |service|
13 | service = service.service
14 | computed_hash = compute_service_hash(service)
15 |
16 | db_service = RenderService.find_by_service_id(service.id)
17 | if db_service.nil?
18 | send_service_created_event(service, computed_hash)
19 | next
20 | end
21 |
22 | if computed_hash != db_service.object_hash
23 | send_service_updated_event(db_service, service, computed_hash)
24 | end
25 | end
26 |
27 | deleted_services = retrieve_deleted_services(services)
28 | return if deleted_services.empty?
29 |
30 | deleted_services.each do |db_service|
31 | send_service_deleted_event(db_service)
32 | end
33 |
34 | rescue StandardError => e
35 | fail_context_to_sentry!(error: e)
36 | end
37 |
38 | private
39 |
40 | def retrieve_account_services(cursor: nil)
41 | services = @client.services.list(limit: 100, cursor: cursor)
42 | if services.total == 0
43 | return services.data
44 | end
45 |
46 | return services.data.concat(retrieve_account_services(cursor: services.next_cursor))
47 | end
48 |
49 | def retrieve_deleted_services(services)
50 | account_services = RenderService.where(account: @account).pluck(:service_id)
51 | render_services = services.map {|s| s.service.id }
52 | deleted_services = account_services - render_services
53 | RenderService.where(service_id: deleted_services)
54 | end
55 |
56 | def compute_service_hash(service)
57 | Digest::SHA256.hexdigest(service.to_h.to_json)
58 | end
59 |
60 | def send_service_created_event(service, hash)
61 | ActiveRecord::Base.transaction do
62 | RenderService.create!(account: @account, service_id: service.id, object_hash: hash)
63 | Webhook.create!(account: @account, event_type: "service.created", payload: service.to_h)
64 | end
65 | end
66 |
67 | def send_service_updated_event(db_service, service, hash)
68 | case service.suspended
69 | when "suspended"
70 | event_type = "service.suspended"
71 | when "not_suspended"
72 | event_type = "service.live"
73 | else
74 | event_type = "service.updated"
75 | end
76 |
77 | ActiveRecord::Base.transaction do
78 | update = db_service.update!(object_hash: hash)
79 | Webhook.create!(account: @account, event_type: event_type, payload: service.to_h)
80 | end
81 | end
82 |
83 | def send_service_deleted_event(db_service)
84 | payload = {
85 | id: db_service.service_id
86 | }
87 | ActiveRecord::Base.transaction do
88 | Webhook.create!(account: @account, event_type: "service.deleted", payload: payload)
89 | db_service.destroy!
90 | end
91 | end
92 | end
93 | end
94 |
--------------------------------------------------------------------------------
/app/javascript/application.js:
--------------------------------------------------------------------------------
1 | // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
2 | import "@hotwired/turbo-rails"
3 | import "controllers"
4 |
--------------------------------------------------------------------------------
/app/javascript/controllers/application.js:
--------------------------------------------------------------------------------
1 | import { Application } from "@hotwired/stimulus"
2 |
3 | const application = Application.start()
4 |
5 | // Configure Stimulus development experience
6 | application.debug = false
7 | window.Stimulus = application
8 |
9 | export { application }
10 |
--------------------------------------------------------------------------------
/app/javascript/controllers/hello_controller.js:
--------------------------------------------------------------------------------
1 | import { Controller } from "@hotwired/stimulus"
2 |
3 | export default class extends Controller {
4 | connect() {
5 | this.element.textContent = "Hello World!"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/javascript/controllers/index.js:
--------------------------------------------------------------------------------
1 | // Import and register all your controllers from the importmap under controllers/*
2 |
3 | import { application } from "controllers/application"
4 |
5 | // Eager load all controllers defined in the import map under controllers/**/*_controller
6 | import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
7 | eagerLoadControllersFrom("controllers", application)
8 |
9 | // Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
10 | // import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
11 | // lazyLoadControllersFrom("controllers", application)
12 |
--------------------------------------------------------------------------------
/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | # Automatically retry jobs that encountered a deadlock
3 | # retry_on ActiveRecord::Deadlocked
4 |
5 | # Most jobs are safe to ignore if the underlying records are no longer available
6 | # discard_on ActiveJob::DeserializationError
7 | end
8 |
--------------------------------------------------------------------------------
/app/jobs/poll_accounts_job.rb:
--------------------------------------------------------------------------------
1 | class PollAccountsJob < ApplicationJob
2 | queue_as :render_scheduler
3 |
4 | def perform(*args)
5 | Account.active.each do |account|
6 | poll_account(account)
7 | end
8 | end
9 |
10 | private
11 |
12 | def poll_account(account)
13 | case account.configurable_type
14 | when "RenderAccountConfiguration"
15 | PollRenderResourcesJob.perform_later(account.id)
16 | when "NotionAccountConfiguration"
17 | PollNotionResourcesJob.perform_later(account.id)
18 | when "DigitalOceanAccountConfiguration"
19 | PollDigitalOceanResourcesJob.perform_later(account.id)
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/app/jobs/poll_digital_ocean_resources_job.rb:
--------------------------------------------------------------------------------
1 | class PollDigitalOceanResourcesJob < ApplicationJob
2 | queue_as :digital_ocean_poller
3 | discard_on StandardError
4 |
5 | def perform(account_id)
6 | account = Account.find_by!(id: account_id)
7 |
8 | DigitalOcean::PollDroplets.call(account: account)
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/app/jobs/poll_notion_resources_job.rb:
--------------------------------------------------------------------------------
1 | class PollNotionResourcesJob < ApplicationJob
2 | queue_as :notion_poller
3 | discard_on StandardError
4 |
5 | def perform(account_id)
6 | account = Account.find(account_id)
7 |
8 | Notion::PollDatabases.call(account: account)
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/app/jobs/poll_render_resources_job.rb:
--------------------------------------------------------------------------------
1 | class PollRenderResourcesJob < ApplicationJob
2 | queue_as :render_poller
3 | discard_on StandardError
4 |
5 | def perform(account_id)
6 | account = Account.find(account_id)
7 |
8 | #Render::PollJobs.call(account: account)
9 | Render::PollServices.call(account: account)
10 | Render::PollDeployments.call(account: account)
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/app/jobs/push_webhook_to_convoy_job.rb:
--------------------------------------------------------------------------------
1 | class PushWebhookToConvoyJob < ApplicationJob
2 | class PushError < StandardError; end
3 |
4 | queue_as :push_webhooks
5 | retry_on PushError, wait: :exponentially_longer, attempts: 10
6 |
7 | def perform(webhook_id)
8 | webhook = Webhook.find(webhook_id)
9 | return if webhook.sent?
10 |
11 | # Use Convoy Client to push event.
12 | event = Convoy::Event.new(
13 | data: {
14 | owner_id: webhook.account_id,
15 | idempotency_key: webhook.id,
16 | event_type: webhook.event_type,
17 | data: generate_webhook_payload(webhook)
18 | }
19 | )
20 |
21 | res = event.fanout
22 | raise PushError.new res&.response['message'] if res&.response.nil? ||
23 | res&.response['status'] == false
24 |
25 | webhook.update!(status: :sent)
26 | end
27 |
28 | private
29 |
30 | def generate_webhook_payload(webhook)
31 | {
32 | event_type: webhook.event_type,
33 | data: webhook.payload
34 | }
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/app/jobs/push_webhooks_job.rb:
--------------------------------------------------------------------------------
1 | class PushWebhooksJob < ApplicationJob
2 | queue_as :webhooks_delivery_scheduler
3 |
4 | def perform(*args)
5 | Webhook.where(status: :pending).each do |webhook|
6 | PushWebhookToConvoyJob.perform_later(webhook.id)
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/app/lib/api_response/account.rb:
--------------------------------------------------------------------------------
1 | require_relative 'base'
2 |
3 | module ApiResponse
4 | class Account < ApiResponse::Base
5 | def self.retrieved_successfully
6 | "Account retrieved successfully"
7 | end
8 |
9 | def self.created_successfully
10 | "Account created successfully"
11 | end
12 |
13 | def self.create_failed
14 | "Account creation failed"
15 | end
16 |
17 | def self.update_failed
18 | "Account update failed"
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/app/lib/api_response/auth.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'base'
4 |
5 | module ApiResponse
6 | class Auth < ApiResponse::Base
7 | def self.login_successful
8 | 'Login successful'
9 | end
10 |
11 | def self.logout_successful
12 | 'Logout successful'
13 | end
14 |
15 | def self.missing_token
16 | 'Missing token'
17 | end
18 |
19 | def self.invalid_token
20 | 'Invalid token'
21 | end
22 |
23 | def self.invalid_credentials
24 | 'Invalid credentials'
25 | end
26 |
27 | def self.set_password
28 | 'Please go and set your password'
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/app/lib/api_response/base.rb:
--------------------------------------------------------------------------------
1 | module ApiResponse
2 | class Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/lib/api_response/user.rb:
--------------------------------------------------------------------------------
1 | require_relative 'base'
2 |
3 | module ApiResponse
4 | class User < Base
5 | def self.invalid_access_token
6 | 'Invalid access token'
7 | end
8 |
9 | def self.registration_successful
10 | 'User sign in success'
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/lib/auth/authenticate_user.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Auth
4 | class AuthenticateUser
5 | def initialize(email, keep_me_in)
6 | @email = email
7 | @keep_me_in = keep_me_in
8 | end
9 |
10 | # Service entry point
11 | def call
12 | payload = { user_id: user.id } if user
13 | time = @keep_me_in ? ENV['JWT_LONG_EXPIRY_TIME'].to_i.days.from_now(Time.now) : nil
14 | args = [payload, time].compact
15 | token = JsonWebToken.encode(*args)
16 |
17 | create_whitelist_jwt(payload)
18 | token
19 | end
20 |
21 | private
22 |
23 | attr_reader :email
24 |
25 | # verify user credentials
26 | def user
27 | user = User.find_by(email: email.downcase)
28 |
29 | if user.nil?
30 | # raise Authentication error if credentials are invalid
31 | raise ExceptionHandler::AuthenticationError,
32 | ApiResponse::Auth.invalid_credentials
33 | end
34 |
35 | user
36 | end
37 |
38 | def create_whitelist_jwt(payload)
39 | user = User.find(payload[:user_id])
40 |
41 | WhitelistedJwt.create!(
42 | jti: payload[:jti],
43 | aud: payload[:aud],
44 | exp: Time.at(payload[:exp]),
45 | user: user
46 | )
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/app/lib/auth/authorize_api_request.rb:
--------------------------------------------------------------------------------
1 | module Auth
2 | class AuthorizeApiRequest
3 | include Helper
4 |
5 | def initialize(headers = {})
6 | @headers = headers
7 | end
8 |
9 | # Service entry point - return valid user object
10 | def call
11 | {
12 | user: user
13 | }
14 | end
15 |
16 | private
17 |
18 | attr_reader :headers
19 |
20 | def user
21 | # check if user is in the database
22 | # memoize user object
23 | @user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token
24 |
25 | WhitelistedJwt.find_by!(jti: decoded_auth_token[:jti])
26 |
27 | return @user
28 | # handle user not found
29 | rescue ActiveRecord::RecordNotFound => e
30 | # raise custom error
31 | raise ExceptionHandler::InvalidToken, "#{ApiResponse::Auth.invalid_token}"
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/app/lib/auth/helper.rb:
--------------------------------------------------------------------------------
1 | module Auth
2 | module Helper
3 |
4 | private
5 |
6 | # decode authentication token
7 | def decoded_auth_token
8 | @decoded_auth_token ||= JsonWebToken.decode(http_auth_header)
9 | end
10 |
11 | # check for token in `Authorization` header
12 | def http_auth_header
13 | if headers['Authorization'].present?
14 | return headers['Authorization'].split(' ').last
15 | end
16 |
17 | raise ExceptionHandler::MissingToken, ApiResponse::Auth.missing_token
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/app/lib/auth/invalidate_api_token.rb:
--------------------------------------------------------------------------------
1 | module Auth
2 | class InvalidateApiToken
3 | include Helper
4 |
5 | def initialize(headers)
6 | @headers = headers
7 | end
8 |
9 | # Service entry point
10 | def call
11 | jwt = WhitelistedJwt.find_by!(jti: decoded_auth_token[:jti]) if decoded_auth_token
12 | jwt.destroy!
13 | end
14 |
15 | private
16 | attr_reader :headers
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/app/lib/exception_handler.rb:
--------------------------------------------------------------------------------
1 | module ExceptionHandler
2 | class AuthenticationError < StandardError; end
3 | class MissingToken < StandardError; end
4 | class InvalidToken < StandardError; end
5 | class ExpiredToken < StandardError; end
6 | end
7 |
--------------------------------------------------------------------------------
/app/lib/json_web_token.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'exception_handler'
4 |
5 | class JsonWebToken
6 | def self.encode(payload,
7 | time = ENV['JWT_DEFAULT_EXPIRY_TIME'].to_i.minutes.from_now(Time.now))
8 |
9 | payload[:exp] = time.to_i
10 | payload[:jti] = create_jti
11 | payload[:aud] = ENV['JWT_AUD']
12 |
13 | JWT.encode(payload, ENV['JWT_SECRET_KEY'], algorithm = 'HS256', header_fields = { typ: 'JWT' })
14 | end
15 |
16 | def self.decode(token)
17 | body = JWT.decode(token, ENV['JWT_SECRET_KEY'], true, { algorithm: 'HS256' })[0]
18 | HashWithIndifferentAccess.new(body)
19 | rescue JWT::DecodeError => e
20 | raise ExceptionHandler::InvalidToken, e.message
21 | end
22 |
23 | def self.create_jti
24 | jti_raw = [ENV['JWT_SECRET_KEY'], Time.now.to_i].join(':').to_s
25 | Digest::MD5.hexdigest(jti_raw)
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: "from@example.com"
3 | layout "mailer"
4 | end
5 |
--------------------------------------------------------------------------------
/app/models/account.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: accounts
4 | #
5 | # id :uuid not null, primary key
6 | # configurable_type :string
7 | # name :string not null
8 | # portal_link_url :string
9 | # status :integer default("active")
10 | # sync_start_datetime :datetime not null
11 | # created_at :datetime not null
12 | # updated_at :datetime not null
13 | # configurable_id :uuid
14 | # user_id :uuid not null
15 | #
16 | # Indexes
17 | #
18 | # index_accounts_on_configurable (configurable_type,configurable_id)
19 | # index_accounts_on_user_id (user_id)
20 | #
21 | # Foreign Keys
22 | #
23 | # fk_rails_... (user_id => users.id)
24 | #
25 | class Account < ApplicationRecord
26 | belongs_to :configurable, polymorphic: true, dependent: :destroy
27 | belongs_to :user
28 | has_many :webhooks, dependent: :delete_all
29 | has_many :render_services, dependent: :delete_all
30 | has_many :notion_databases, dependent: :delete_all
31 |
32 |
33 | STATUSES = { active: 0, inactive: 1 }.freeze
34 | enum status: STATUSES
35 | end
36 |
--------------------------------------------------------------------------------
/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | primary_abstract_class
3 | end
4 |
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/app/models/concerns/.keep
--------------------------------------------------------------------------------
/app/models/concerns/platform_configuration_validator.rb:
--------------------------------------------------------------------------------
1 | class PlatformConfigurationValidator < ActiveModel::Validator
2 | def validate(record)
3 | case record.class.name
4 | when "NotionDatabase"
5 | verify_notion_configuration(record)
6 | when "RenderAccountConfiguration"
7 | verify_render_configuration(record)
8 | when "DigitalOceanAccountConfiguration"
9 | verify_digital_ocean_configuration(record)
10 | end
11 | end
12 |
13 | private
14 |
15 | def verify_render_configuration(record)
16 | client = RenderRuby::Client.new(api_key: record.api_key)
17 | client.owners.list
18 |
19 | rescue RenderRuby::Error => e
20 | record.errors.add(:config, "invalid render api key")
21 | end
22 |
23 | def verify_digital_ocean_configuration(record)
24 | client = DropletKit::Client.new(access_token: record.access_token)
25 | client.account.info
26 |
27 | rescue StandardError => e
28 | if e.message.starts_with?("401")
29 | record.errors.add(:config, "invalid digital ocean access token")
30 | return
31 | end
32 |
33 | record.errors.add(:config, "an error occurred validating your digital ocean config")
34 | end
35 |
36 | def verify_notion_configuration(record)
37 | client = Notion::Client.new(token: record.account.configurable.access_token)
38 | client.database_query(database_id: record.database_id)
39 |
40 | rescue Notion::Api::Errors::NotionError => e
41 | if e.message.starts_with?("path failed validation")
42 | record.errors.add(:config, "#{record.database_id} is invalid")
43 | return
44 | end
45 |
46 | record.errors.add(:config,
47 | "an error occurred validating your database id, please try again")
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/app/models/digital_ocean_account_configuration.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: digital_ocean_account_configurations
4 | #
5 | # id :uuid not null, primary key
6 | # access_token :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | #
10 | class DigitalOceanAccountConfiguration < ApplicationRecord
11 | validates_with PlatformConfigurationValidator
12 |
13 | has_one :account, as: :configurable, class_name: 'Account'
14 | end
15 |
--------------------------------------------------------------------------------
/app/models/digital_ocean_droplet.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: digital_ocean_droplets
4 | #
5 | # id :uuid not null, primary key
6 | # object_hash :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | # account_id :uuid
10 | # droplet_id :string not null
11 | #
12 | # Indexes
13 | #
14 | # index_digital_ocean_droplets_on_account_id (account_id)
15 | # index_digital_ocean_droplets_on_account_id_and_droplet_id (account_id,droplet_id) UNIQUE
16 | #
17 | # Foreign Keys
18 | #
19 | # fk_rails_... (account_id => accounts.id)
20 | #
21 | class DigitalOceanDroplet < ApplicationRecord
22 | belongs_to :account
23 | end
24 |
--------------------------------------------------------------------------------
/app/models/notion_account_configuration.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: notion_account_configurations
4 | #
5 | # id :uuid not null, primary key
6 | # access_token :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | #
10 | # Indexes
11 | #
12 | # index_notion_account_configurations_on_access_token (access_token)
13 | #
14 | class NotionAccountConfiguration < ApplicationRecord
15 | has_one :account, as: :configurable, class_name: 'Account'
16 | has_many :notion_databases
17 | end
18 |
--------------------------------------------------------------------------------
/app/models/notion_database.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: notion_databases
4 | #
5 | # id :uuid not null, primary key
6 | # created_at :datetime not null
7 | # updated_at :datetime not null
8 | # account_id :uuid
9 | # database_id :string not null
10 | #
11 | # Indexes
12 | #
13 | # index_notion_databases_on_account_id (account_id)
14 | # unique_database_per_account (account_id,database_id) UNIQUE
15 | #
16 | # Foreign Keys
17 | #
18 | # fk_rails_... (account_id => accounts.id)
19 | #
20 | class NotionDatabase < ApplicationRecord
21 | validates_with PlatformConfigurationValidator
22 |
23 | belongs_to :account
24 | has_many :notion_rows, dependent: :delete_all
25 | end
26 |
--------------------------------------------------------------------------------
/app/models/notion_row.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: notion_rows
4 | #
5 | # id :uuid not null, primary key
6 | # object_hash :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | # notion_database_id :uuid
10 | # row_id :string
11 | #
12 | # Indexes
13 | #
14 | # index_notion_rows_on_notion_database_id (notion_database_id)
15 | # index_notion_rows_on_notion_database_id_and_row_id (notion_database_id,row_id) UNIQUE
16 | #
17 | # Foreign Keys
18 | #
19 | # fk_rails_... (notion_database_id => notion_databases.id) ON DELETE => cascade
20 | #
21 | class NotionRow < ApplicationRecord
22 | belongs_to :notion_database
23 | end
24 |
--------------------------------------------------------------------------------
/app/models/profile.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: profiles
4 | #
5 | # id :uuid not null, primary key
6 | # firstname :string not null
7 | # lastname :string not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | # user_id :uuid
11 | #
12 | # Indexes
13 | #
14 | # index_profiles_on_user_id (user_id)
15 | #
16 | # Foreign Keys
17 | #
18 | # fk_rails_... (user_id => users.id)
19 | #
20 | class Profile < ApplicationRecord
21 | belongs_to :user
22 |
23 | def fullname
24 | "#{self.firstname} #{self.lastname}"
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/app/models/render_account_configuration.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: render_account_configurations
4 | #
5 | # id :uuid not null, primary key
6 | # api_key :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | #
10 | # Indexes
11 | #
12 | # index_render_account_configurations_on_api_key (api_key) UNIQUE
13 | #
14 | class RenderAccountConfiguration < ApplicationRecord
15 | validates_with PlatformConfigurationValidator
16 |
17 | has_one :account, as: :configurable, class_name: 'Account'
18 | end
19 |
--------------------------------------------------------------------------------
/app/models/render_deployment.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: render_deployments
4 | #
5 | # id :uuid not null, primary key
6 | # object_hash :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | # deployment_id :string not null
10 | # render_service_id :uuid
11 | #
12 | # Indexes
13 | #
14 | # index_render_deployments_on_deployment_id (deployment_id) UNIQUE
15 | # index_render_deployments_on_render_service_id (render_service_id)
16 | #
17 | # Foreign Keys
18 | #
19 | # fk_rails_... (render_service_id => render_services.id) ON DELETE => cascade
20 | #
21 | class RenderDeployment < ApplicationRecord
22 | belongs_to :render_service
23 | end
24 |
--------------------------------------------------------------------------------
/app/models/render_service.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: render_services
4 | #
5 | # id :uuid not null, primary key
6 | # object_hash :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | # account_id :uuid
10 | # service_id :string not null
11 | #
12 | # Indexes
13 | #
14 | # index_render_services_on_account_id (account_id)
15 | # index_render_services_on_service_id (service_id) UNIQUE
16 | #
17 | # Foreign Keys
18 | #
19 | # fk_rails_... (account_id => accounts.id)
20 | #
21 | class RenderService < ApplicationRecord
22 | belongs_to :account
23 | has_many :render_deployments, dependent: :delete_all
24 | end
25 |
--------------------------------------------------------------------------------
/app/models/service.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: services
4 | #
5 | # id :uuid not null, primary key
6 | # name :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | #
10 | # Indexes
11 | #
12 | # index_services_on_name (name) UNIQUE
13 | #
14 | class Service < ApplicationRecord
15 | end
16 |
--------------------------------------------------------------------------------
/app/models/user.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: users
4 | #
5 | # id :uuid not null, primary key
6 | # email :string default(""), not null
7 | # encrypted_password :string default(""), not null
8 | # provider :string
9 | # remember_created_at :datetime
10 | # reset_password_sent_at :datetime
11 | # reset_password_token :string
12 | # created_at :datetime not null
13 | # updated_at :datetime not null
14 | #
15 | # Indexes
16 | #
17 | # index_users_on_email (email) UNIQUE
18 | # index_users_on_reset_password_token (reset_password_token) UNIQUE
19 | #
20 | class User < ApplicationRecord
21 | # Include default devise modules. Others available are:
22 | # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
23 | devise :database_authenticatable, :registerable,
24 | :recoverable, :rememberable, :validatable
25 |
26 | has_one :profile
27 | has_many :accounts
28 |
29 | def self.create_user_for_google(data)
30 | ActiveRecord::Base.transaction do
31 | user = where(email: data["email"]).first_or_initialize.tap do |user|
32 | user.provider="google_oauth2"
33 | user.email=data["email"]
34 | user.password=Devise.friendly_token[0,20]
35 | user.password_confirmation=user.password
36 | user.save!
37 | end
38 |
39 | Profile.find_or_create_by(
40 | user: user,
41 | firstname: data['given_name'],
42 | lastname: data['family_name']
43 | )
44 |
45 | user
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/app/models/webhook.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: webhooks
4 | #
5 | # id :uuid not null, primary key
6 | # event_type :string not null
7 | # payload :json not null
8 | # status :integer default("pending")
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | # account_id :uuid
12 | #
13 | # Indexes
14 | #
15 | # index_webhooks_on_account_id (account_id)
16 | #
17 | # Foreign Keys
18 | #
19 | # fk_rails_... (account_id => accounts.id)
20 | #
21 | class Webhook < ApplicationRecord
22 | belongs_to :account
23 |
24 | STATUSES = { pending: 0, sent: 1 }.freeze
25 | enum status: STATUSES
26 | end
27 |
--------------------------------------------------------------------------------
/app/models/webhook_subscription.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: webhook_subscriptions
4 | #
5 | # id :uuid not null, primary key
6 | # created_at :datetime not null
7 | # updated_at :datetime not null
8 | # convoy_endpoint_id :string not null
9 | # convoy_subscription_id :string not null
10 | #
11 | class WebhookSubscription < ApplicationRecord
12 | end
13 |
--------------------------------------------------------------------------------
/app/models/whitelisted_jwt.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: whitelisted_jwts
4 | #
5 | # id :uuid not null, primary key
6 | # aud :string
7 | # exp :datetime not null
8 | # jti :string not null
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | # user_id :uuid
12 | #
13 | # Indexes
14 | #
15 | # index_whitelisted_jwts_on_jti (jti) UNIQUE
16 | # index_whitelisted_jwts_on_user_id (user_id)
17 | #
18 | # Foreign Keys
19 | #
20 | # fk_rails_... (user_id => users.id)
21 | #
22 | class WhitelistedJwt < ApplicationRecord
23 | belongs_to :user
24 | end
25 |
--------------------------------------------------------------------------------
/app/serializers/account_serializer.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: accounts
4 | #
5 | # id :uuid not null, primary key
6 | # configurable_type :string
7 | # name :string not null
8 | # portal_link_url :string
9 | # status :integer default("active")
10 | # sync_start_datetime :datetime not null
11 | # created_at :datetime not null
12 | # updated_at :datetime not null
13 | # configurable_id :uuid
14 | # user_id :uuid not null
15 | #
16 | # Indexes
17 | #
18 | # index_accounts_on_configurable (configurable_type,configurable_id)
19 | # index_accounts_on_user_id (user_id)
20 | #
21 | # Foreign Keys
22 | #
23 | # fk_rails_... (user_id => users.id)
24 | #
25 | class AccountSerializer < ApplicationSerializer
26 | attributes :id, :name, :type, :portal_link_url, :metadata
27 |
28 | def type
29 | account_type
30 | end
31 |
32 | def metadata
33 | case account_type
34 | when "render"
35 | {
36 | "api_key": object.configurable.api_key
37 | }
38 | when "notion"
39 | {
40 | "databases": object.notion_databases.pluck(:database_id)
41 | }
42 | when "digital_ocean"
43 | {
44 | "access_token": object.configurable.access_token
45 | }
46 | end
47 | end
48 |
49 | private
50 |
51 | def account_type
52 | case object.configurable_type
53 | when "RenderAccountConfiguration"
54 | "render"
55 | when "NotionAccountConfiguration"
56 | "notion"
57 | when "DigitalOceanAccountConfiguration"
58 | "digital_ocean"
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/app/serializers/application_serializer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationSerializer < ActiveModel::Serializer
4 | end
5 |
--------------------------------------------------------------------------------
/app/serializers/base_model_serializer.rb:
--------------------------------------------------------------------------------
1 | class BaseModelSerializer < ApplicationSerializer
2 | attributes :status, :message, :data
3 |
4 | def data
5 | serializer = ActiveModel::Serializer.serializer_for(object.data)
6 | return {} unless serializer
7 |
8 | return serializer.new(object.data) if object.data.respond_to? :each
9 | serializer.new(object.data).serializable_hash
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/serializers/models/base_model.rb:
--------------------------------------------------------------------------------
1 | module Models
2 | class BaseModel < ActiveModelSerializers::Model
3 | attributes :status, :message, :data
4 | end
5 | end
6 |
7 |
--------------------------------------------------------------------------------
/app/serializers/models/registration_response.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Models
4 | class RegistrationResponse < ActiveModelSerializers::Model
5 | attributes :user, :profile, :token
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/app/serializers/registration_response_serializer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class RegistrationResponseSerializer < ApplicationSerializer
4 | attributes :email, :firstname, :lastname, :token
5 |
6 | def email
7 | object.user.email
8 | end
9 |
10 | def firstname
11 | object.profile.firstname
12 | end
13 |
14 | def lastname
15 | object.profile.lastname
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/app/views/devise/confirmations/new.html.erb:
--------------------------------------------------------------------------------
1 |
Resend confirmation instructions
2 |
3 | <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
4 | <%= render "devise/shared/error_messages", resource: resource %>
5 |
6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
9 |
10 |
11 |
12 | <%= f.submit "Resend confirmation instructions" %>
13 |
14 | <% end %>
15 |
16 | <%= render "devise/shared/links" %>
17 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/confirmation_instructions.html.erb:
--------------------------------------------------------------------------------
1 | Welcome <%= @email %>!
2 |
3 | You can confirm your account email through the link below:
4 |
5 | <%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
6 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/email_changed.html.erb:
--------------------------------------------------------------------------------
1 | Hello <%= @email %>!
2 |
3 | <% if @resource.try(:unconfirmed_email?) %>
4 | We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.
5 | <% else %>
6 | We're contacting you to notify you that your email has been changed to <%= @resource.email %>.
7 | <% end %>
8 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/password_change.html.erb:
--------------------------------------------------------------------------------
1 | Hello <%= @resource.email %>!
2 |
3 | We're contacting you to notify you that your password has been changed.
4 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/reset_password_instructions.html.erb:
--------------------------------------------------------------------------------
1 | Hello <%= @resource.email %>!
2 |
3 | Someone has requested a link to change your password. You can do this through the link below.
4 |
5 | <%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>
6 |
7 | If you didn't request this, please ignore this email.
8 | Your password won't change until you access the link above and create a new one.
9 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/unlock_instructions.html.erb:
--------------------------------------------------------------------------------
1 | Hello <%= @resource.email %>!
2 |
3 | Your account has been locked due to an excessive number of unsuccessful sign in attempts.
4 |
5 | Click the link below to unlock your account:
6 |
7 | <%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>
8 |
--------------------------------------------------------------------------------
/app/views/devise/passwords/edit.html.erb:
--------------------------------------------------------------------------------
1 | Change your password
2 |
3 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
4 | <%= render "devise/shared/error_messages", resource: resource %>
5 | <%= f.hidden_field :reset_password_token %>
6 |
7 |
8 | <%= f.label :password, "New password" %>
9 | <% if @minimum_password_length %>
10 | (<%= @minimum_password_length %> characters minimum)
11 | <% end %>
12 | <%= f.password_field :password, autofocus: true, autocomplete: "new-password" %>
13 |
14 |
15 |
16 | <%= f.label :password_confirmation, "Confirm new password" %>
17 | <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
18 |
19 |
20 |
21 | <%= f.submit "Change my password" %>
22 |
23 | <% end %>
24 |
25 | <%= render "devise/shared/links" %>
26 |
--------------------------------------------------------------------------------
/app/views/devise/passwords/new.html.erb:
--------------------------------------------------------------------------------
1 | Forgot your password?
2 |
3 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
4 | <%= render "devise/shared/error_messages", resource: resource %>
5 |
6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
9 |
10 |
11 |
12 | <%= f.submit "Send me reset password instructions" %>
13 |
14 | <% end %>
15 |
16 | <%= render "devise/shared/links" %>
17 |
--------------------------------------------------------------------------------
/app/views/devise/registrations/edit.html.erb:
--------------------------------------------------------------------------------
1 | Edit <%= resource_name.to_s.humanize %>
2 |
3 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
4 | <%= render "devise/shared/error_messages", resource: resource %>
5 |
6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
9 |
10 |
11 | <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
12 | Currently waiting confirmation for: <%= resource.unconfirmed_email %>
13 | <% end %>
14 |
15 |
16 | <%= f.label :password %> (leave blank if you don't want to change it)
17 | <%= f.password_field :password, autocomplete: "new-password" %>
18 | <% if @minimum_password_length %>
19 |
20 | <%= @minimum_password_length %> characters minimum
21 | <% end %>
22 |
23 |
24 |
25 | <%= f.label :password_confirmation %>
26 | <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
27 |
28 |
29 |
30 | <%= f.label :current_password %> (we need your current password to confirm your changes)
31 | <%= f.password_field :current_password, autocomplete: "current-password" %>
32 |
33 |
34 |
35 | <%= f.submit "Update" %>
36 |
37 | <% end %>
38 |
39 | Cancel my account
40 |
41 | Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %>
42 |
43 | <%= link_to "Back", :back %>
44 |
--------------------------------------------------------------------------------
/app/views/devise/registrations/new.html.erb:
--------------------------------------------------------------------------------
1 | Sign up
2 |
3 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
4 | <%= render "devise/shared/error_messages", resource: resource %>
5 |
6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
9 |
10 |
11 |
12 | <%= f.label :password %>
13 | <% if @minimum_password_length %>
14 | (<%= @minimum_password_length %> characters minimum)
15 | <% end %>
16 | <%= f.password_field :password, autocomplete: "new-password" %>
17 |
18 |
19 |
20 | <%= f.label :password_confirmation %>
21 | <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
22 |
23 |
24 |
25 | <%= f.submit "Sign up" %>
26 |
27 | <% end %>
28 |
29 | <%= render "devise/shared/links" %>
30 |
--------------------------------------------------------------------------------
/app/views/devise/sessions/new.html.erb:
--------------------------------------------------------------------------------
1 | Log in
2 |
3 | <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
4 |
5 | <%= f.label :email %>
6 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
7 |
8 |
9 |
10 | <%= f.label :password %>
11 | <%= f.password_field :password, autocomplete: "current-password" %>
12 |
13 |
14 | <% if devise_mapping.rememberable? %>
15 |
16 | <%= f.check_box :remember_me %>
17 | <%= f.label :remember_me %>
18 |
19 | <% end %>
20 |
21 |
22 | <%= f.submit "Log in" %>
23 |
24 | <% end %>
25 |
26 | <%= render "devise/shared/links" %>
27 |
--------------------------------------------------------------------------------
/app/views/devise/shared/_error_messages.html.erb:
--------------------------------------------------------------------------------
1 | <% if resource.errors.any? %>
2 |
3 |
4 | <%= I18n.t("errors.messages.not_saved",
5 | count: resource.errors.count,
6 | resource: resource.class.model_name.human.downcase)
7 | %>
8 |
9 |
10 | <% resource.errors.full_messages.each do |message| %>
11 | - <%= message %>
12 | <% end %>
13 |
14 |
15 | <% end %>
16 |
--------------------------------------------------------------------------------
/app/views/devise/shared/_links.html.erb:
--------------------------------------------------------------------------------
1 | <%- if controller_name != 'sessions' %>
2 | <%= link_to "Log in", new_session_path(resource_name) %>
3 | <% end %>
4 |
5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %>
6 | <%= link_to "Sign up", new_registration_path(resource_name) %>
7 | <% end %>
8 |
9 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
10 | <%= link_to "Forgot your password?", new_password_path(resource_name) %>
11 | <% end %>
12 |
13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
14 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
15 | <% end %>
16 |
17 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
19 | <% end %>
20 |
21 | <%- if devise_mapping.omniauthable? %>
22 | <%- resource_class.omniauth_providers.each do |provider| %>
23 | <%= button_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), data: { turbo: false } %>
24 | <% end %>
25 | <% end %>
26 |
--------------------------------------------------------------------------------
/app/views/devise/unlocks/new.html.erb:
--------------------------------------------------------------------------------
1 | Resend unlock instructions
2 |
3 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
4 | <%= render "devise/shared/error_messages", resource: resource %>
5 |
6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
9 |
10 |
11 |
12 | <%= f.submit "Resend unlock instructions" %>
13 |
14 | <% end %>
15 |
16 | <%= render "devise/shared/links" %>
17 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | UnofficalWebhooks
5 |
6 | <%= csrf_meta_tags %>
7 | <%= csp_meta_tag %>
8 |
9 | <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
10 | <%= javascript_importmap_tags %>
11 |
12 |
13 |
14 | <%= yield %>
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/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", __dir__)
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_requirement
64 | @bundler_requirement ||=
65 | env_var_version || cli_arg_version ||
66 | bundler_requirement_for(lockfile_version)
67 | end
68 |
69 | def bundler_requirement_for(version)
70 | return "#{Gem::Requirement.default}.a" unless version
71 |
72 | bundler_gem_version = Gem::Version.new(version)
73 |
74 | requirement = bundler_gem_version.approximate_recommendation
75 |
76 | return requirement unless Gem.rubygems_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/importmap:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require_relative "../config/application"
4 | require "importmap/commands"
5 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | require_relative "boot"
2 |
3 | require "rails/all"
4 |
5 | # Require the gems listed in Gemfile, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(*Rails.groups)
8 |
9 | module UnofficalWebhooks
10 | class Application < Rails::Application
11 | # Initialize configuration defaults for originally generated Rails version.
12 | config.load_defaults 7.0
13 |
14 | # Configuration for the application, engines, and railties goes here.
15 | #
16 | # These settings can be overridden in specific environments using the files
17 | # in config/environments, which are processed later.
18 | #
19 | # config.time_zone = "Central Time (US & Canada)"
20 | # config.eager_load_paths << Rails.root.join("extras")
21 |
22 | # ActiveJob Queueing backend
23 | config.active_job.queue_adapter = :sidekiq
24 |
25 | config.middleware.insert_before 0, Rack::Cors do
26 | allow do
27 | origins '*'
28 | resource '*',
29 | headers: :any,
30 | expose: %w[authorization access-token expiry
31 | token-type uid client],
32 | methods: %i[get post options delete put]
33 | end
34 | end
35 |
36 | # ActiveRecord Schema Format
37 | config.active_record.schema_format = :sql
38 |
39 | config.generators do |g|
40 | g.orm :active_record, primary_key_type: :uuid
41 | end
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
2 |
3 | require "bundler/setup" # Set up gems listed in the Gemfile.
4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations.
5 |
--------------------------------------------------------------------------------
/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: redis
3 | url: redis://localhost:6379/1
4 |
5 | test:
6 | adapter: test
7 |
8 | production:
9 | adapter: redis
10 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
11 | channel_prefix: unoffical_webhooks_production
12 |
--------------------------------------------------------------------------------
/config/clock.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'clockwork'
4 | require 'active_support/time'
5 | require './config/boot'
6 | require './config/environment'
7 |
8 | module Clockwork
9 | handler do |job|
10 | job.camelize.constantize.perform_later
11 | end
12 |
13 | poll_interval = ENV['POLL_INTERVAL'].to_i.seconds
14 | every(poll_interval, 'poll_accounts_job')
15 |
16 | push_webhooks_interval = ENV['PUSH_WEBHOOKS_INTERVAL'].to_i.seconds
17 | every(push_webhooks_interval, 'push_webhooks_job')
18 | end
19 |
--------------------------------------------------------------------------------
/config/credentials.yml.enc:
--------------------------------------------------------------------------------
1 | /9afK/cg38VOSyUWT0qE1RxFAolevHFsPI/XxRIrhCIY8IfzLOQfIaxu0mimC5pUWzkMb19WIuXtVKwJP1leM34T9fFz2+P2qtJo+aAhS8tcm3e2KdFoW5+QtqFMdbcxGLX2A/lVjr9Dp1QEjaJL//W/fVoyQCoPqFb1uQhY/zCaf0c7/rFCzmP3j2+MiE48Y/hGawSTCP+t49ElFu3GN8gc8POxavaytY4OHoC/XP3zcWmJEazhM/UkBNxsqQcs21YrSmzDN7+EZGlU8sJlYohurwRZhHjSQ5AUiu9Ke5Rv+lEVZflZo594rVzMAmXm+ypV/hpdWackcMm7snoh/rYRh6wkpgY0J4KTc83RE7K80XBWa850fX8Wg/kvBuPEQFTMpICMO/lwgj+BbNgsrzVR8w6aNRCLzSP1--YtZG58ye5FKYdaFv--cOJSAl+IjB/3sDlfCDZxMQ==
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite. Versions 3.8.0 and up are supported.
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem "sqlite3"
6 | #
7 | default: &default
8 | adapter: postgresql
9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
10 | timeout: 5000
11 |
12 | development:
13 | <<: *default
14 | url: <%= ENV['DATABASE_URL'] %>
15 |
16 | # Warning: The database defined as "test" will be erased and
17 | # re-generated from your development database when you run "rake".
18 | # Do not set this db to the same as development or production.
19 | test:
20 | <<: *default
21 | url: <%= ENV['DATABASE_URL'] %>
22 |
23 | production:
24 | <<: *default
25 | pool: 20
26 | url: <%= ENV['DATABASE_URL'] %>
27 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative "application"
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # In the development environment your application's code is reloaded any time
7 | # it changes. This slows down response time but is perfect for development
8 | # since you don't have to restart the web server when you make code changes.
9 | config.cache_classes = false
10 |
11 | # Do not eager load code on boot.
12 | config.eager_load = false
13 |
14 | # Show full error reports.
15 | config.consider_all_requests_local = true
16 |
17 | # Enable server timing
18 | config.server_timing = true
19 |
20 | # Enable/disable caching. By default caching is disabled.
21 | # Run rails dev:cache to toggle caching.
22 | if Rails.root.join("tmp/caching-dev.txt").exist?
23 | config.action_controller.perform_caching = true
24 | config.action_controller.enable_fragment_cache_logging = true
25 |
26 | config.cache_store = :memory_store
27 | config.public_file_server.headers = {
28 | "Cache-Control" => "public, max-age=#{2.days.to_i}"
29 | }
30 | else
31 | config.action_controller.perform_caching = false
32 |
33 | config.cache_store = :null_store
34 | end
35 |
36 | # Store uploaded files on the local file system (see config/storage.yml for options).
37 | config.active_storage.service = :local
38 |
39 | # Don't care if the mailer can't send.
40 | config.action_mailer.raise_delivery_errors = false
41 |
42 | config.action_mailer.perform_caching = false
43 |
44 | # Print deprecation notices to the Rails logger.
45 | config.active_support.deprecation = :log
46 |
47 | # Raise exceptions for disallowed deprecations.
48 | config.active_support.disallowed_deprecation = :raise
49 |
50 | # Tell Active Support which deprecation messages to disallow.
51 | config.active_support.disallowed_deprecation_warnings = []
52 |
53 | # Raise an error on page load if there are pending migrations.
54 | config.active_record.migration_error = :page_load
55 |
56 | # Highlight code that triggered database queries in logs.
57 | config.active_record.verbose_query_logs = true
58 |
59 | # Suppress logger output for asset requests.
60 | config.assets.quiet = true
61 |
62 | # Raises error for missing translations.
63 | # config.i18n.raise_on_missing_translations = true
64 |
65 | # Annotate rendered view with file names.
66 | # config.action_view.annotate_rendered_view_with_filenames = true
67 |
68 | # Uncomment if you wish to allow Action Cable access from any origin.
69 | # config.action_cable.disable_request_forgery_protection = true
70 | end
71 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | Rails.application.configure do
4 | # Settings specified here will take precedence over those in config/application.rb.
5 |
6 | # Code is not reloaded between requests.
7 | config.cache_classes = true
8 |
9 | # Eager load code on boot. This eager loads most of Rails and
10 | # your application in memory, allowing both threaded web servers
11 | # and those relying on copy on write to perform better.
12 | # Rake tasks automatically ignore this option for performance.
13 | config.eager_load = true
14 |
15 | # Full error reports are disabled and caching is turned on.
16 | config.consider_all_requests_local = false
17 | config.action_controller.perform_caching = true
18 |
19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
21 | # config.require_master_key = true
22 |
23 | # Disable serving static files from the `/public` folder by default since
24 | # Apache or NGINX already handles this.
25 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?
26 |
27 | # Compress CSS using a preprocessor.
28 | # config.assets.css_compressor = :sass
29 |
30 | # Do not fallback to assets pipeline if a precompiled asset is missed.
31 | config.assets.compile = false
32 |
33 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
34 | # config.asset_host = "http://assets.example.com"
35 |
36 | # Specifies the header that your server uses for sending files.
37 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
38 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX
39 |
40 | # Store uploaded files on the local file system (see config/storage.yml for options).
41 | config.active_storage.service = :local
42 |
43 | # Mount Action Cable outside main process or domain.
44 | # config.action_cable.mount_path = nil
45 | # config.action_cable.url = "wss://example.com/cable"
46 | # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ]
47 |
48 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
49 | # config.force_ssl = true
50 |
51 | # Include generic and useful information about system operation, but avoid logging too much
52 | # information to avoid inadvertent exposure of personally identifiable information (PII).
53 | config.log_level = :info
54 |
55 | # Prepend all log lines with the following tags.
56 | config.log_tags = [ :request_id ]
57 |
58 | # Use a different cache store in production.
59 | # config.cache_store = :mem_cache_store
60 |
61 | # Use a real queuing backend for Active Job (and separate queues per environment).
62 | # config.active_job.queue_adapter = :resque
63 | # config.active_job.queue_name_prefix = "unoffical_webhooks_production"
64 |
65 | config.action_mailer.perform_caching = false
66 |
67 | # Ignore bad email addresses and do not raise email delivery errors.
68 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
69 | # config.action_mailer.raise_delivery_errors = false
70 |
71 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
72 | # the I18n.default_locale when a translation cannot be found).
73 | config.i18n.fallbacks = true
74 |
75 | # Don't log any deprecations.
76 | config.active_support.report_deprecations = false
77 |
78 | # Use default logging formatter so that PID and timestamp are not suppressed.
79 | config.log_formatter = ::Logger::Formatter.new
80 |
81 | # Use a different logger for distributed setups.
82 | # require "syslog/logger"
83 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name")
84 |
85 | if ENV["RAILS_LOG_TO_STDOUT"].present?
86 | logger = ActiveSupport::Logger.new(STDOUT)
87 | logger.formatter = config.log_formatter
88 | config.logger = ActiveSupport::TaggedLogging.new(logger)
89 | end
90 |
91 | # Do not dump schema after migrations.
92 | config.active_record.dump_schema_after_migration = false
93 | end
94 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | require "active_support/core_ext/integer/time"
2 |
3 | # The test environment is used exclusively to run your application's
4 | # test suite. You never need to work with it otherwise. Remember that
5 | # your test database is "scratch space" for the test suite and is wiped
6 | # and recreated between test runs. Don't rely on the data there!
7 |
8 | Rails.application.configure do
9 | # Settings specified here will take precedence over those in config/application.rb.
10 |
11 | # Turn false under Spring and add config.action_view.cache_template_loading = true.
12 | config.cache_classes = true
13 |
14 | # Eager loading loads your whole application. When running a single test locally,
15 | # this probably isn't necessary. It's a good idea to do in a continuous integration
16 | # system, or in some way before deploying your code.
17 | config.eager_load = ENV["CI"].present?
18 |
19 | # Configure public file server for tests with Cache-Control for performance.
20 | config.public_file_server.enabled = true
21 | config.public_file_server.headers = {
22 | "Cache-Control" => "public, max-age=#{1.hour.to_i}"
23 | }
24 |
25 | # Show full error reports and disable caching.
26 | config.consider_all_requests_local = true
27 | config.action_controller.perform_caching = false
28 | config.cache_store = :null_store
29 |
30 | # Raise exceptions instead of rendering exception templates.
31 | config.action_dispatch.show_exceptions = false
32 |
33 | # Disable request forgery protection in test environment.
34 | config.action_controller.allow_forgery_protection = false
35 |
36 | # Store uploaded files on the local file system in a temporary directory.
37 | config.active_storage.service = :test
38 |
39 | config.action_mailer.perform_caching = false
40 |
41 | # Tell Action Mailer not to deliver emails to the real world.
42 | # The :test delivery method accumulates sent emails in the
43 | # ActionMailer::Base.deliveries array.
44 | config.action_mailer.delivery_method = :test
45 |
46 | # Print deprecation notices to the stderr.
47 | config.active_support.deprecation = :stderr
48 |
49 | # Raise exceptions for disallowed deprecations.
50 | config.active_support.disallowed_deprecation = :raise
51 |
52 | # Tell Active Support which deprecation messages to disallow.
53 | config.active_support.disallowed_deprecation_warnings = []
54 |
55 | # Raises error for missing translations.
56 | # config.i18n.raise_on_missing_translations = true
57 |
58 | # Annotate rendered view with file names.
59 | # config.action_view.annotate_rendered_view_with_filenames = true
60 | end
61 |
--------------------------------------------------------------------------------
/config/importmap.rb:
--------------------------------------------------------------------------------
1 | # Pin npm packages by running ./bin/importmap
2 |
3 | pin "application", preload: true
4 | pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
5 | pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
6 | pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
7 | pin_all_from "app/javascript/controllers", under: "controllers"
8 |
--------------------------------------------------------------------------------
/config/initializers/active_model_serializers.rb:
--------------------------------------------------------------------------------
1 | ActiveModelSerializers.config.default_includes = '**'
2 |
--------------------------------------------------------------------------------
/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = "1.0"
5 |
6 | # Add additional assets to the asset load path.
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 |
9 | # Precompile additional assets.
10 | # application.js, application.css, and all non-JS/CSS in the app/assets
11 | # folder are already added.
12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
13 |
--------------------------------------------------------------------------------
/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide content security policy.
4 | # See the Securing Rails Applications Guide for more information:
5 | # https://guides.rubyonrails.org/security.html#content-security-policy-header
6 |
7 | # Rails.application.configure do
8 | # config.content_security_policy do |policy|
9 | # policy.default_src :self, :https
10 | # policy.font_src :self, :https, :data
11 | # policy.img_src :self, :https, :data
12 | # policy.object_src :none
13 | # policy.script_src :self, :https
14 | # policy.style_src :self, :https
15 | # # Specify URI for violation reports
16 | # # policy.report_uri "/csp-violation-report-endpoint"
17 | # end
18 | #
19 | # # Generate session nonces for permitted importmap and inline scripts
20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
21 | # config.content_security_policy_nonce_directives = %w(script-src)
22 | #
23 | # # Report violations without enforcing the policy.
24 | # # config.content_security_policy_report_only = true
25 | # end
26 |
--------------------------------------------------------------------------------
/config/initializers/convoy.rb:
--------------------------------------------------------------------------------
1 | require 'convoy'
2 |
3 | Convoy.ssl = true
4 | Convoy.api_key = ENV['CONVOY_API_KEY']
5 | Convoy.project_id = ENV['CONVOY_PROJECT_ID']
6 | Convoy.path_version = 'v1'
7 |
--------------------------------------------------------------------------------
/config/initializers/devise.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Assuming you have not yet modified this file, each configuration option below
4 | # is set to its default value. Note that some are commented out while others
5 | # are not: uncommented lines are intended to protect your configuration from
6 | # breaking changes in upgrades (i.e., in the event that future versions of
7 | # Devise change the default values for those options).
8 | #
9 | # Use this hook to configure devise mailer, warden hooks and so forth.
10 | # Many of these configuration options can be set straight in your model.
11 | Devise.setup do |config|
12 | # The secret key used by Devise. Devise uses this key to generate
13 | # random tokens. Changing this key will render invalid all existing
14 | # confirmation, reset password and unlock tokens in the database.
15 | # Devise will use the `secret_key_base` as its `secret_key`
16 | # by default. You can change it below and use your own secret key.
17 | # config.secret_key = 'a027a5b3f285096b3db280d5634f87b2b9d633efbe488fb5d744a9253efa5b41b9af9053a8123440f37fe95525be9f0afe6530815708e8c115b11fe49d245b94'
18 |
19 | # ==> Controller configuration
20 | # Configure the parent class to the devise controllers.
21 | # config.parent_controller = 'DeviseController'
22 |
23 | # ==> Mailer Configuration
24 | # Configure the e-mail address which will be shown in Devise::Mailer,
25 | # note that it will be overwritten if you use your own mailer class
26 | # with default "from" parameter.
27 | config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
28 |
29 | # Configure the class responsible to send e-mails.
30 | # config.mailer = 'Devise::Mailer'
31 |
32 | # Configure the parent class responsible to send e-mails.
33 | # config.parent_mailer = 'ActionMailer::Base'
34 |
35 | # ==> ORM configuration
36 | # Load and configure the ORM. Supports :active_record (default) and
37 | # :mongoid (bson_ext recommended) by default. Other ORMs may be
38 | # available as additional gems.
39 | require 'devise/orm/active_record'
40 |
41 | # ==> Configuration for any authentication mechanism
42 | # Configure which keys are used when authenticating a user. The default is
43 | # just :email. You can configure it to use [:username, :subdomain], so for
44 | # authenticating a user, both parameters are required. Remember that those
45 | # parameters are used only when authenticating and not when retrieving from
46 | # session. If you need permissions, you should implement that in a before filter.
47 | # You can also supply a hash where the value is a boolean determining whether
48 | # or not authentication should be aborted when the value is not present.
49 | # config.authentication_keys = [:email]
50 |
51 | # Configure parameters from the request object used for authentication. Each entry
52 | # given should be a request method and it will automatically be passed to the
53 | # find_for_authentication method and considered in your model lookup. For instance,
54 | # if you set :request_keys to [:subdomain], :subdomain will be used on authentication.
55 | # The same considerations mentioned for authentication_keys also apply to request_keys.
56 | # config.request_keys = []
57 |
58 | # Configure which authentication keys should be case-insensitive.
59 | # These keys will be downcased upon creating or modifying a user and when used
60 | # to authenticate or find a user. Default is :email.
61 | config.case_insensitive_keys = [:email]
62 |
63 | # Configure which authentication keys should have whitespace stripped.
64 | # These keys will have whitespace before and after removed upon creating or
65 | # modifying a user and when used to authenticate or find a user. Default is :email.
66 | config.strip_whitespace_keys = [:email]
67 |
68 | # Tell if authentication through request.params is enabled. True by default.
69 | # It can be set to an array that will enable params authentication only for the
70 | # given strategies, for example, `config.params_authenticatable = [:database]` will
71 | # enable it only for database (email + password) authentication.
72 | # config.params_authenticatable = true
73 |
74 | # Tell if authentication through HTTP Auth is enabled. False by default.
75 | # It can be set to an array that will enable http authentication only for the
76 | # given strategies, for example, `config.http_authenticatable = [:database]` will
77 | # enable it only for database authentication.
78 | # For API-only applications to support authentication "out-of-the-box", you will likely want to
79 | # enable this with :database unless you are using a custom strategy.
80 | # The supported strategies are:
81 | # :database = Support basic authentication with authentication key + password
82 | # config.http_authenticatable = false
83 |
84 | # If 401 status code should be returned for AJAX requests. True by default.
85 | # config.http_authenticatable_on_xhr = true
86 |
87 | # The realm used in Http Basic Authentication. 'Application' by default.
88 | # config.http_authentication_realm = 'Application'
89 |
90 | # It will change confirmation, password recovery and other workflows
91 | # to behave the same regardless if the e-mail provided was right or wrong.
92 | # Does not affect registerable.
93 | # config.paranoid = true
94 |
95 | # By default Devise will store the user in session. You can skip storage for
96 | # particular strategies by setting this option.
97 | # Notice that if you are skipping storage for all authentication paths, you
98 | # may want to disable generating routes to Devise's sessions controller by
99 | # passing skip: :sessions to `devise_for` in your config/routes.rb
100 | config.skip_session_storage = [:http_auth]
101 |
102 | # By default, Devise cleans up the CSRF token on authentication to
103 | # avoid CSRF token fixation attacks. This means that, when using AJAX
104 | # requests for sign in and sign up, you need to get a new CSRF token
105 | # from the server. You can disable this option at your own risk.
106 | # config.clean_up_csrf_token_on_authentication = true
107 |
108 | # When false, Devise will not attempt to reload routes on eager load.
109 | # This can reduce the time taken to boot the app but if your application
110 | # requires the Devise mappings to be loaded during boot time the application
111 | # won't boot properly.
112 | # config.reload_routes = true
113 |
114 | # ==> Configuration for :database_authenticatable
115 | # For bcrypt, this is the cost for hashing the password and defaults to 12. If
116 | # using other algorithms, it sets how many times you want the password to be hashed.
117 | # The number of stretches used for generating the hashed password are stored
118 | # with the hashed password. This allows you to change the stretches without
119 | # invalidating existing passwords.
120 | #
121 | # Limiting the stretches to just one in testing will increase the performance of
122 | # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use
123 | # a value less than 10 in other environments. Note that, for bcrypt (the default
124 | # algorithm), the cost increases exponentially with the number of stretches (e.g.
125 | # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation).
126 | config.stretches = Rails.env.test? ? 1 : 12
127 |
128 | # Set up a pepper to generate the hashed password.
129 | # config.pepper = '32ae4ebdfa8b2d4a60c4f0e49f328267e5677fdd69f5b811ebcd14fea11ee145ea3358d000f02d30e5c13d09fd47a0f1cdf77f4671457bb2d0806193037bb039'
130 |
131 | # Send a notification to the original email when the user's email is changed.
132 | # config.send_email_changed_notification = false
133 |
134 | # Send a notification email when the user's password is changed.
135 | # config.send_password_change_notification = false
136 |
137 | # ==> Configuration for :confirmable
138 | # A period that the user is allowed to access the website even without
139 | # confirming their account. For instance, if set to 2.days, the user will be
140 | # able to access the website for two days without confirming their account,
141 | # access will be blocked just in the third day.
142 | # You can also set it to nil, which will allow the user to access the website
143 | # without confirming their account.
144 | # Default is 0.days, meaning the user cannot access the website without
145 | # confirming their account.
146 | # config.allow_unconfirmed_access_for = 2.days
147 |
148 | # A period that the user is allowed to confirm their account before their
149 | # token becomes invalid. For example, if set to 3.days, the user can confirm
150 | # their account within 3 days after the mail was sent, but on the fourth day
151 | # their account can't be confirmed with the token any more.
152 | # Default is nil, meaning there is no restriction on how long a user can take
153 | # before confirming their account.
154 | # config.confirm_within = 3.days
155 |
156 | # If true, requires any email changes to be confirmed (exactly the same way as
157 | # initial account confirmation) to be applied. Requires additional unconfirmed_email
158 | # db field (see migrations). Until confirmed, new email is stored in
159 | # unconfirmed_email column, and copied to email column on successful confirmation.
160 | config.reconfirmable = true
161 |
162 | # Defines which key will be used when confirming an account
163 | # config.confirmation_keys = [:email]
164 |
165 | # ==> Configuration for :rememberable
166 | # The time the user will be remembered without asking for credentials again.
167 | # config.remember_for = 2.weeks
168 |
169 | # Invalidates all the remember me tokens when the user signs out.
170 | config.expire_all_remember_me_on_sign_out = true
171 |
172 | # If true, extends the user's remember period when remembered via cookie.
173 | # config.extend_remember_period = false
174 |
175 | # Options to be passed to the created cookie. For instance, you can set
176 | # secure: true in order to force SSL only cookies.
177 | # config.rememberable_options = {}
178 |
179 | # ==> Configuration for :validatable
180 | # Range for password length.
181 | config.password_length = 6..128
182 |
183 | # Email regex used to validate email formats. It simply asserts that
184 | # one (and only one) @ exists in the given string. This is mainly
185 | # to give user feedback and not to assert the e-mail validity.
186 | config.email_regexp = /\A[^@\s]+@[^@\s]+\z/
187 |
188 | # ==> Configuration for :timeoutable
189 | # The time you want to timeout the user session without activity. After this
190 | # time the user will be asked for credentials again. Default is 30 minutes.
191 | # config.timeout_in = 30.minutes
192 |
193 | # ==> Configuration for :lockable
194 | # Defines which strategy will be used to lock an account.
195 | # :failed_attempts = Locks an account after a number of failed attempts to sign in.
196 | # :none = No lock strategy. You should handle locking by yourself.
197 | # config.lock_strategy = :failed_attempts
198 |
199 | # Defines which key will be used when locking and unlocking an account
200 | # config.unlock_keys = [:email]
201 |
202 | # Defines which strategy will be used to unlock an account.
203 | # :email = Sends an unlock link to the user email
204 | # :time = Re-enables login after a certain amount of time (see :unlock_in below)
205 | # :both = Enables both strategies
206 | # :none = No unlock strategy. You should handle unlocking by yourself.
207 | # config.unlock_strategy = :both
208 |
209 | # Number of authentication tries before locking an account if lock_strategy
210 | # is failed attempts.
211 | # config.maximum_attempts = 20
212 |
213 | # Time interval to unlock the account if :time is enabled as unlock_strategy.
214 | # config.unlock_in = 1.hour
215 |
216 | # Warn on the last attempt before the account is locked.
217 | # config.last_attempt_warning = true
218 |
219 | # ==> Configuration for :recoverable
220 | #
221 | # Defines which key will be used when recovering the password for an account
222 | # config.reset_password_keys = [:email]
223 |
224 | # Time interval you can reset your password with a reset password key.
225 | # Don't put a too small interval or your users won't have the time to
226 | # change their passwords.
227 | config.reset_password_within = 6.hours
228 |
229 | # When set to false, does not sign a user in automatically after their password is
230 | # reset. Defaults to true, so a user is signed in automatically after a reset.
231 | # config.sign_in_after_reset_password = true
232 |
233 | # ==> Configuration for :encryptable
234 | # Allow you to use another hashing or encryption algorithm besides bcrypt (default).
235 | # You can use :sha1, :sha512 or algorithms from others authentication tools as
236 | # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20
237 | # for default behavior) and :restful_authentication_sha1 (then you should set
238 | # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper).
239 | #
240 | # Require the `devise-encryptable` gem when using anything other than bcrypt
241 | # config.encryptor = :sha512
242 |
243 | # ==> Scopes configuration
244 | # Turn scoped views on. Before rendering "sessions/new", it will first check for
245 | # "users/sessions/new". It's turned off by default because it's slower if you
246 | # are using only default views.
247 | # config.scoped_views = false
248 |
249 | # Configure the default scope given to Warden. By default it's the first
250 | # devise role declared in your routes (usually :user).
251 | # config.default_scope = :user
252 |
253 | # Set this configuration to false if you want /users/sign_out to sign out
254 | # only the current scope. By default, Devise signs out all scopes.
255 | # config.sign_out_all_scopes = true
256 |
257 | # ==> Navigation configuration
258 | # Lists the formats that should be treated as navigational. Formats like
259 | # :html should redirect to the sign in page when the user does not have
260 | # access, but formats like :xml or :json, should return 401.
261 | #
262 | # If you have any extra navigational formats, like :iphone or :mobile, you
263 | # should add them to the navigational formats lists.
264 | #
265 | # The "*/*" below is required to match Internet Explorer requests.
266 | # config.navigational_formats = ['*/*', :html, :turbo_stream]
267 |
268 | # The default HTTP method used to sign out a resource. Default is :delete.
269 | config.sign_out_via = :delete
270 |
271 | # ==> OmniAuth
272 | # Add a new OmniAuth provider. Check the wiki for more information on setting
273 | # up on your models and hooks.
274 | config.omniauth :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET'], {}
275 |
276 | # ==> Warden configuration
277 | # If you want to use other strategies, that are not supported by Devise, or
278 | # change the failure app, you can configure them inside the config.warden block.
279 | #
280 | # config.warden do |manager|
281 | # manager.intercept_401 = false
282 | # manager.default_strategies(scope: :user).unshift :some_external_strategy
283 | # end
284 |
285 | # ==> Mountable engine configurations
286 | # When using Devise inside an engine, let's call it `MyEngine`, and this engine
287 | # is mountable, there are some extra configurations to be taken into account.
288 | # The following options are available, assuming the engine is mounted as:
289 | #
290 | # mount MyEngine, at: '/my_engine'
291 | #
292 | # The router that invoked `devise_for`, in the example above, would be:
293 | # config.router_name = :my_engine
294 | #
295 | # When using OmniAuth, Devise cannot automatically set OmniAuth path,
296 | # so you need to do it manually. For the users scope, it would be:
297 | # config.omniauth_path_prefix = '/my_engine/users/auth'
298 |
299 | # ==> Hotwire/Turbo configuration
300 | # When using Devise with Hotwire/Turbo, the http status for error responses
301 | # and some redirects must match the following. The default in Devise for existing
302 | # apps is `200 OK` and `302 Found respectively`, but new apps are generated with
303 | # these new defaults that match Hotwire/Turbo behavior.
304 | # Note: These might become the new default in future versions of Devise.
305 | config.responder.error_status = :unprocessable_entity
306 | config.responder.redirect_status = :see_other
307 |
308 | # ==> Configuration for :registerable
309 |
310 | # When set to false, does not sign a user in automatically after their password is
311 | # changed. Defaults to true, so a user is signed in automatically after changing a password.
312 | # config.sign_in_after_change_password = true
313 | end
314 |
--------------------------------------------------------------------------------
/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure parameters to be filtered from the log file. Use this to limit dissemination of
4 | # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported
5 | # notations and behaviors.
6 | Rails.application.config.filter_parameters += [
7 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
8 | ]
9 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, "\\1en"
8 | # inflect.singular /^(ox)en/i, "\\1"
9 | # inflect.irregular "person", "people"
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym "RESTful"
16 | # end
17 |
--------------------------------------------------------------------------------
/config/initializers/permissions_policy.rb:
--------------------------------------------------------------------------------
1 | # Define an application-wide HTTP permissions policy. For further
2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy
3 | #
4 | # Rails.application.config.permissions_policy do |f|
5 | # f.camera :none
6 | # f.gyroscope :none
7 | # f.microphone :none
8 | # f.usb :none
9 | # f.fullscreen :self
10 | # f.payment :self, "https://secure.example.com"
11 | # end
12 |
--------------------------------------------------------------------------------
/config/initializers/sentry.rb:
--------------------------------------------------------------------------------
1 | Sentry.init do |config|
2 | config.dsn = ENV['SENTRY_DSN']
3 | config.breadcrumbs_logger = [:active_support_logger, :sentry_logger]
4 |
5 | # Set tracesSampleRate to 1.0 to capture 100%
6 | # of transactions for performance monitoring.
7 | # We recommend adjusting this value in production
8 | config.traces_sample_rate = 0.5
9 | # or
10 | config.traces_sampler = lambda do |context|
11 | true
12 | end
13 | end
14 |
15 |
--------------------------------------------------------------------------------
/config/initializers/sidekiq.rb:
--------------------------------------------------------------------------------
1 | Sidekiq.configure_server do |config|
2 | config.redis = { url: ENV['REDIS_URL'] }
3 | end
4 |
5 | Sidekiq.configure_client do |config|
6 | config.redis = { url: ENV['REDIS_URL'] }
7 | end
8 |
--------------------------------------------------------------------------------
/config/locales/devise.en.yml:
--------------------------------------------------------------------------------
1 | # Additional translations at https://github.com/heartcombo/devise/wiki/I18n
2 |
3 | en:
4 | devise:
5 | confirmations:
6 | confirmed: "Your email address has been successfully confirmed."
7 | send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes."
8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
9 | failure:
10 | already_authenticated: "You are already signed in."
11 | inactive: "Your account is not activated yet."
12 | invalid: "Invalid %{authentication_keys} or password."
13 | locked: "Your account is locked."
14 | last_attempt: "You have one more attempt before your account is locked."
15 | not_found_in_database: "Invalid %{authentication_keys} or password."
16 | timeout: "Your session expired. Please sign in again to continue."
17 | unauthenticated: "You need to sign in or sign up before continuing."
18 | unconfirmed: "You have to confirm your email address before continuing."
19 | mailer:
20 | confirmation_instructions:
21 | subject: "Confirmation instructions"
22 | reset_password_instructions:
23 | subject: "Reset password instructions"
24 | unlock_instructions:
25 | subject: "Unlock instructions"
26 | email_changed:
27 | subject: "Email Changed"
28 | password_change:
29 | subject: "Password Changed"
30 | omniauth_callbacks:
31 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
32 | success: "Successfully authenticated from %{kind} account."
33 | passwords:
34 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
35 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
36 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
37 | updated: "Your password has been changed successfully. You are now signed in."
38 | updated_not_active: "Your password has been changed successfully."
39 | registrations:
40 | destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon."
41 | signed_up: "Welcome! You have signed up successfully."
42 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
43 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
44 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
45 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirmation link to confirm your new email address."
46 | updated: "Your account has been updated successfully."
47 | updated_but_not_signed_in: "Your account has been updated successfully, but since your password was changed, you need to sign in again."
48 | sessions:
49 | signed_in: "Signed in successfully."
50 | signed_out: "Signed out successfully."
51 | already_signed_out: "Signed out successfully."
52 | unlocks:
53 | send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes."
54 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes."
55 | unlocked: "Your account has been unlocked successfully. Please sign in to continue."
56 | errors:
57 | messages:
58 | already_confirmed: "was already confirmed, please try signing in"
59 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
60 | expired: "has expired, please request a new one"
61 | not_found: "not found"
62 | not_locked: "was not locked"
63 | not_saved:
64 | one: "1 error prohibited this %{resource} from being saved:"
65 | other: "%{count} errors prohibited this %{resource} from being saved:"
66 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t "hello"
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t("hello") %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # "true": "foo"
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at https://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
9 | threads min_threads_count, max_threads_count
10 |
11 | # Specifies the `worker_timeout` threshold that Puma will use to wait before
12 | # terminating a worker in development environments.
13 | #
14 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
15 |
16 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
17 | #
18 | port ENV.fetch("PORT") { 3000 }
19 |
20 | # Specifies the `environment` that Puma will run in.
21 | #
22 | environment ENV.fetch("RAILS_ENV") { "development" }
23 |
24 | # Specifies the `pidfile` that Puma will use.
25 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
26 |
27 | # Specifies the number of `workers` to boot in clustered mode.
28 | # Workers are forked web server processes. If using threads and workers together
29 | # the concurrency of the application would be max `threads` * `workers`.
30 | # Workers do not work on JRuby or Windows (both of which do not support
31 | # processes).
32 | #
33 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
34 |
35 | # Use the `preload_app!` method when specifying a `workers` number.
36 | # This directive tells Puma to first boot the application and load code
37 | # before forking the application. This takes advantage of Copy On Write
38 | # process behavior so workers use less memory.
39 | #
40 | # preload_app!
41 |
42 | # Allow puma to be restarted by `bin/rails restart` command.
43 | plugin :tmp_restart
44 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | require 'sidekiq/web'
2 |
3 | Rails.application.routes.draw do
4 | devise_for :users
5 | # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
6 |
7 | # Defines the root path route ("/")
8 | # root "articles#index"
9 |
10 | Sidekiq::Web.use Rack::Auth::Basic do |username, password|
11 | username == ENV['SIDEKIQ_USERNAME'] && password == ENV['SIDEKIQ_PASSWORD']
12 | end
13 | mount Sidekiq::Web => '/sidekiq'
14 |
15 | namespace :api do
16 | namespace :v1 do
17 | resources :accounts
18 | end
19 | end
20 |
21 | post 'api/v1/google_oauth', to: 'authentication#google_oauth'
22 | end
23 |
--------------------------------------------------------------------------------
/config/sidekiq.yml:
--------------------------------------------------------------------------------
1 | ---
2 | :concurrency: 5
3 | development:
4 | :queues:
5 | - push_webhooks
6 | - render_poller
7 | - notion_poller
8 | - digital_ocean_poller
9 | - render_scheduler
10 | - webhooks_delivery_scheduler
11 | staging:
12 | :concurrency: 10
13 | :queues:
14 | - push_webhooks
15 | - render_poller
16 | - notion_poller
17 | - digital_ocean_poller
18 | - render_scheduler
19 | - webhooks_delivery_scheduler
20 | production:
21 | :concurrency: 10
22 | :queues:
23 | - push_webhooks
24 | - render_poller
25 | - notion_poller
26 | - digital_ocean_poller
27 | - render_scheduler
28 | - webhooks_delivery_scheduler
29 |
--------------------------------------------------------------------------------
/config/storage.yml:
--------------------------------------------------------------------------------
1 | test:
2 | service: Disk
3 | root: <%= Rails.root.join("tmp/storage") %>
4 |
5 | local:
6 | service: Disk
7 | root: <%= Rails.root.join("storage") %>
8 |
9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10 | # amazon:
11 | # service: S3
12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14 | # region: us-east-1
15 | # bucket: your_own_bucket-<%= Rails.env %>
16 |
17 | # Remember not to checkin your GCS keyfile to a repository
18 | # google:
19 | # service: GCS
20 | # project: your_project
21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
22 | # bucket: your_own_bucket-<%= Rails.env %>
23 |
24 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25 | # microsoft:
26 | # service: AzureStorage
27 | # storage_account_name: your_account_name
28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
29 | # container: your_container_name-<%= Rails.env %>
30 |
31 | # mirror:
32 | # service: Mirror
33 | # primary: local
34 | # mirrors: [ amazon, google, microsoft ]
35 |
--------------------------------------------------------------------------------
/db/migrate/0_enable_pgcrypto_extension.rb:
--------------------------------------------------------------------------------
1 | class EnablePgcryptoExtension < ActiveRecord::Migration[7.0]
2 | def change
3 | enable_extension 'uuid-ossp'
4 | enable_extension 'pgcrypto'
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20230617044757_create_services.rb:
--------------------------------------------------------------------------------
1 | class CreateServices < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :services, id: :uuid do |t|
4 | t.string :name, null: false
5 |
6 | t.index :name, unique: true
7 |
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20230617105153_create_render_accounts.rb:
--------------------------------------------------------------------------------
1 | class CreateRenderAccounts < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :render_accounts, id: :uuid do |t|
4 | t.string :name, null: false
5 | t.string :api_key, null: false
6 | t.datetime :last_poll_time, null: false
7 |
8 | t.index [:name, :api_key], unique: true
9 |
10 | t.timestamps
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/db/migrate/20230617172518_create_render_services.rb:
--------------------------------------------------------------------------------
1 | class CreateRenderServices < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :render_services, id: :uuid do |t|
4 | t.string :service_id, null: false
5 | t.string :object_hash, null: false
6 | t.references :render_account, foreign_key: true, type: :uuid
7 |
8 | t.index :service_id, unique: true
9 |
10 | t.timestamps
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/db/migrate/20230617205232_create_webhooks.rb:
--------------------------------------------------------------------------------
1 | class CreateWebhooks < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :webhooks, id: :uuid do |t|
4 | t.string :event_type, null: false
5 | t.json :payload, null: false
6 | t.integer :status, default: 0
7 |
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20230618062335_create_render_deployments.rb:
--------------------------------------------------------------------------------
1 | class CreateRenderDeployments < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :render_deployments, id: :uuid do |t|
4 | t.string :deployment_id, null: false
5 | t.string :object_hash, null: false
6 | t.references :render_service, foreign_key: true, type: :uuid
7 |
8 | t.index :deployment_id, unique: true
9 |
10 | t.timestamps
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/db/migrate/20230618160227_create_webhook_subscriptions.rb:
--------------------------------------------------------------------------------
1 | class CreateWebhookSubscriptions < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :webhook_subscriptions, id: :uuid do |t|
4 | t.string :convoy_endpoint_id, null: false
5 | t.string :convoy_subscription_id, null: false
6 |
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/db/migrate/20230618170032_change_render_accounts_to_accounts.rb:
--------------------------------------------------------------------------------
1 | class ChangeRenderAccountsToAccounts < ActiveRecord::Migration[7.0]
2 | def change
3 | remove_column :render_accounts, :api_key, :string
4 | rename_table :render_accounts, :accounts
5 | add_reference :accounts, :configurable, polymorphic: true, type: :uuid
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/db/migrate/20230618171524_create_render_account_configurations.rb:
--------------------------------------------------------------------------------
1 | class CreateRenderAccountConfigurations < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :render_account_configurations, id: :uuid do |t|
4 | t.string :api_key, null: false
5 |
6 | t.index :api_key, unique: true
7 |
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20230618181856_devise_create_users.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class DeviseCreateUsers < ActiveRecord::Migration[7.0]
4 | def change
5 | create_table :users, id: :uuid do |t|
6 | ## Database authenticatable
7 | t.string :email, null: false, default: ""
8 | t.string :encrypted_password, null: false, default: ""
9 |
10 | ## Recoverable
11 | t.string :reset_password_token
12 | t.datetime :reset_password_sent_at
13 |
14 | ## Rememberable
15 | t.datetime :remember_created_at
16 |
17 | ## Trackable
18 | # t.integer :sign_in_count, default: 0, null: false
19 | # t.datetime :current_sign_in_at
20 | # t.datetime :last_sign_in_at
21 | # t.string :current_sign_in_ip
22 | # t.string :last_sign_in_ip
23 |
24 | ## Confirmable
25 | # t.string :confirmation_token
26 | # t.datetime :confirmed_at
27 | # t.datetime :confirmation_sent_at
28 | # t.string :unconfirmed_email # Only if using reconfirmable
29 |
30 | ## Lockable
31 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
32 | # t.string :unlock_token # Only if unlock strategy is :email or :both
33 | # t.datetime :locked_at
34 |
35 | t.string :provider
36 |
37 | t.timestamps null: false
38 | end
39 |
40 | add_index :users, :email, unique: true
41 | add_index :users, :reset_password_token, unique: true
42 | # add_index :users, :confirmation_token, unique: true
43 | # add_index :users, :unlock_token, unique: true
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/db/migrate/20230619080041_create_whitelisted_jwts.rb:
--------------------------------------------------------------------------------
1 | class CreateWhitelistedJwts < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :whitelisted_jwts, id: :uuid do |t|
4 | t.string :jti, null: false
5 | t.string :aud
6 | t.datetime :exp, null: false
7 | t.references :user, foreign_key: true, type: :uuid
8 |
9 | t.index :jti, unique: true
10 |
11 | t.timestamps
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/db/migrate/20230619111134_create_profiles.rb:
--------------------------------------------------------------------------------
1 | class CreateProfiles < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :profiles, id: :uuid do |t|
4 | t.string :firstname, null: false
5 | t.string :lastname, null: false
6 | t.references :user, foreign_key: true, type: :uuid
7 |
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20230619140156_add_user_to_accounts.rb:
--------------------------------------------------------------------------------
1 | class AddUserToAccounts < ActiveRecord::Migration[7.0]
2 | def change
3 | add_reference :accounts, :user, foreign_key: true, type: :uuid, null: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20230621104650_add_accounts_to_webhooks.rb:
--------------------------------------------------------------------------------
1 | class AddAccountsToWebhooks < ActiveRecord::Migration[7.0]
2 | def change
3 | add_reference :webhooks, :account, foreign_key: true, type: :uuid
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20230621121811_add_portal_link_to_account.rb:
--------------------------------------------------------------------------------
1 | class AddPortalLinkToAccount < ActiveRecord::Migration[7.0]
2 | def change
3 | add_column :accounts, :portal_link_url, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20230621133250_modify_render_object_references.rb:
--------------------------------------------------------------------------------
1 | class ModifyRenderObjectReferences < ActiveRecord::Migration[7.0]
2 | def change
3 | rename_column :render_services, :render_account_id, :account_id
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20230623065032_create_notion_account_configurations.rb:
--------------------------------------------------------------------------------
1 | class CreateNotionAccountConfigurations < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :notion_account_configurations, id: :uuid do |t|
4 | t.string :access_token, null: false
5 |
6 | t.index :access_token
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/db/migrate/20230623065709_create_notion_databases.rb:
--------------------------------------------------------------------------------
1 | class CreateNotionDatabases < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :notion_databases, id: :uuid do |t|
4 | t.string :database_id, null: false
5 | t.references :account, foreign_key: true, type: :uuid
6 |
7 | t.index [:account_id, :database_id], unique: true,
8 | name: :unique_database_per_account
9 |
10 | t.timestamps
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/db/migrate/20230623070129_create_notion_rows.rb:
--------------------------------------------------------------------------------
1 | class CreateNotionRows < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :notion_rows, id: :uuid do |t|
4 | t.string :row_id
5 | t.string :object_hash, null: false
6 | t.references :notion_database, foreign_key: true, type: :uuid
7 |
8 | t.index [:notion_database_id, :row_id], unique: true
9 |
10 | t.timestamps
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/db/migrate/20230627045713_add_fields_to_account.rb:
--------------------------------------------------------------------------------
1 | class AddFieldsToAccount < ActiveRecord::Migration[7.0]
2 | def change
3 | add_column :accounts, :status, :integer, default: 0
4 | rename_column :accounts, :last_poll_time, :sync_start_datetime
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20230627200008_create_digital_ocean_account_configurations.rb:
--------------------------------------------------------------------------------
1 | class CreateDigitalOceanAccountConfigurations < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :digital_ocean_account_configurations, id: :uuid do |t|
4 | t.string :access_token, null: false
5 |
6 | t.timestamps
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/db/migrate/20230627202802_create_digital_ocean_droplets.rb:
--------------------------------------------------------------------------------
1 | class CreateDigitalOceanDroplets < ActiveRecord::Migration[7.0]
2 | def change
3 | create_table :digital_ocean_droplets, id: :uuid do |t|
4 | t.string :droplet_id, null: false
5 | t.string :object_hash, null: false
6 | t.references :account, foreign_key: true, type: :uuid
7 |
8 | t.index [:account_id, :droplet_id], unique: true
9 |
10 | t.timestamps
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/db/migrate/20230830142630_change_cascading_deletes.rb:
--------------------------------------------------------------------------------
1 | class ChangeCascadingDeletes < ActiveRecord::Migration[7.0]
2 | def change
3 | remove_foreign_key :render_deployments, :render_services
4 | remove_foreign_key :notion_rows, :notion_databases
5 | add_foreign_key :render_deployments, :render_services, on_delete: :cascade
6 | add_foreign_key :notion_rows, :notion_databases, on_delete: :cascade
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/docker-compose.dev.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | volumes:
4 | loki_data:
5 | redis_data:
6 | postgres_data:
7 |
8 | services:
9 | redis:
10 | image: redis:6.2-alpine
11 | container_name: unofficial_redis
12 | restart: on-failure
13 | volumes:
14 | - ./tmp/redis_data:/data
15 | ports:
16 | - 6379:6379
17 |
18 | postgres:
19 | image: postgres:15.3-alpine3.18
20 | container_name: unofficial_db
21 | restart: on-failure
22 | environment:
23 | POSTGRES_DB: unofficialdb
24 | POSTGRES_USER: postgres
25 | POSTGRES_PASSWORD: postgres
26 | PGDATA: /data/postgres
27 | volumes:
28 | - ./tmp/postgres_data:/data/postgres
29 | ports:
30 | - 5432:5432
31 |
32 | loki:
33 | image: grafana/loki
34 | container_name: unofficial_loki
35 | restart: on-failure
36 | volumes:
37 | - ./tmp/loki_data:/var/lib/loki
38 | ports:
39 | - 9000:3000
40 |
--------------------------------------------------------------------------------
/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/lib/assets/.keep
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/lib/tasks/.keep
--------------------------------------------------------------------------------
/lib/tasks/auto_annotate_models.rake:
--------------------------------------------------------------------------------
1 | # NOTE: only doing this in development as some production environments (Heroku)
2 | # NOTE: are sensitive to local FS writes, and besides -- it's just not proper
3 | # NOTE: to have a dev-mode tool do its thing in production.
4 | if Rails.env.development?
5 | require 'annotate'
6 | task :set_annotation_options do
7 | # You can override any of these by setting an environment variable of the
8 | # same name.
9 | Annotate.set_defaults(
10 | 'active_admin' => 'false',
11 | 'additional_file_patterns' => [],
12 | 'routes' => 'false',
13 | 'models' => 'true',
14 | 'position_in_routes' => 'before',
15 | 'position_in_class' => 'before',
16 | 'position_in_test' => 'before',
17 | 'position_in_fixture' => 'before',
18 | 'position_in_factory' => 'before',
19 | 'position_in_serializer' => 'before',
20 | 'show_foreign_keys' => 'true',
21 | 'show_complete_foreign_keys' => 'false',
22 | 'show_indexes' => 'true',
23 | 'simple_indexes' => 'false',
24 | 'model_dir' => 'app/models',
25 | 'root_dir' => '',
26 | 'include_version' => 'false',
27 | 'require' => '',
28 | 'exclude_tests' => 'false',
29 | 'exclude_fixtures' => 'false',
30 | 'exclude_factories' => 'false',
31 | 'exclude_serializers' => 'false',
32 | 'exclude_scaffolds' => 'true',
33 | 'exclude_controllers' => 'true',
34 | 'exclude_helpers' => 'true',
35 | 'exclude_sti_subclasses' => 'false',
36 | 'ignore_model_sub_dir' => 'false',
37 | 'ignore_columns' => nil,
38 | 'ignore_routes' => nil,
39 | 'ignore_unknown_models' => 'false',
40 | 'hide_limit_column_types' => 'integer,bigint,boolean',
41 | 'hide_default_column_types' => 'json,jsonb,hstore',
42 | 'skip_on_db_migrate' => 'false',
43 | 'format_bare' => 'true',
44 | 'format_rdoc' => 'false',
45 | 'format_yard' => 'false',
46 | 'format_markdown' => 'false',
47 | 'sort' => 'false',
48 | 'force' => 'false',
49 | 'frozen' => 'false',
50 | 'classified_sort' => 'true',
51 | 'trace' => 'false',
52 | 'wrapper_open' => nil,
53 | 'wrapper_close' => nil,
54 | 'with_comment' => 'true'
55 | )
56 | end
57 |
58 | Annotate.load_tasks
59 | end
60 |
--------------------------------------------------------------------------------
/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/log/.keep
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/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 |
--------------------------------------------------------------------------------
/render.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | - type: web
3 | name: unofficial-api
4 | env: docker
5 | dockerCommand: ./scripts/start.api.sh
6 | envVars:
7 | - key: REDIS_URL
8 | fromService:
9 | type: redis
10 | name: unofficial-redis
11 | property: connectionString
12 | - key: DATABASE_URL
13 | fromDatabase:
14 | name: unofficial-db
15 | property: connectionString
16 | - fromGroup: prod-env
17 |
18 | - type: worker
19 | name: unofficial-worker
20 | env: docker
21 | dockerCommand: ./scripts/start.worker.sh
22 | envVars:
23 | - key: REDIS_URL
24 | fromService:
25 | type: redis
26 | name: unofficial-redis
27 | property: connectionString
28 | - key: DATABASE_URL
29 | fromDatabase:
30 | name: unofficial-db
31 | property: connectionString
32 | - fromGroup: prod-env
33 |
34 | - type: worker
35 | name: unofficial-cron
36 | env: docker
37 | dockerCommand: ./scripts/start.cron.sh
38 | envVars:
39 | - key: REDIS_URL
40 | fromService:
41 | type: redis
42 | name: unofficial-redis
43 | property: connectionString
44 | - key: DATABASE_URL
45 | fromDatabase:
46 | name: unofficial-db
47 | property: connectionString
48 | - fromGroup: prod-env
49 |
50 | - type: redis
51 | name: unofficial-redis
52 | ipAllowList: []
53 | plan: free
54 | maxmemoryPolicy: allkeys-lfu
55 |
56 | databases:
57 | - name: unofficial-db
58 | databaseName: unofficialdb
59 | user: unofficialuser
60 |
61 | envVarGroups:
62 | - name: prod-env
63 | envVars:
64 | - key: RAILS_ENV
65 | value: production
66 | - key: SECRET_KEY_BASE
67 | sync: false
68 | - key: DATABASE_URL
69 | sync: false
70 | - key: GOOGLE_API
71 | sync: false
72 | - key: JWT_SECRET_KEY
73 | sync: false
74 | - key: JWT_DEFAULT_EXPIRY_TIME
75 | sync: false
76 | - key: JWT_LONG_EXPIRY_TIME
77 | sync: false
78 | - key: POLL_INTERVAL
79 | sync: false
80 | - key: PUSH_WEBHOOKS_INTERVAL
81 | sync: false
82 | - key: CONVOY_API_KEY
83 | sync: false
84 | - key: CONVOY_PROJECT_ID
85 | sync: false
86 | - key: SENTRY_DSN
87 | sync: false
88 |
89 |
--------------------------------------------------------------------------------
/scripts/start.api.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | bundle exec rake db:migrate
4 | bundle exec rails server -u puma -b ::
5 |
--------------------------------------------------------------------------------
/scripts/start.cron.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | bundle exec clockwork config/clock.rb
4 |
--------------------------------------------------------------------------------
/scripts/start.worker.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | bundle exec rake db:migrate
4 | bundle exec sidekiq -e ${RAILS_ENV:-development} -C config/sidekiq.yml
5 |
--------------------------------------------------------------------------------
/spec/factories/digital_ocean_account_configurations.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: digital_ocean_account_configurations
4 | #
5 | # id :uuid not null, primary key
6 | # access_token :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | #
10 | FactoryBot.define do
11 | factory :digital_ocean_account_configuration do
12 |
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/spec/factories/digital_ocean_droplets.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: digital_ocean_droplets
4 | #
5 | # id :uuid not null, primary key
6 | # object_hash :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | # account_id :uuid
10 | # droplet_id :string not null
11 | #
12 | # Indexes
13 | #
14 | # index_digital_ocean_droplets_on_account_id (account_id)
15 | # index_digital_ocean_droplets_on_account_id_and_droplet_id (account_id,droplet_id) UNIQUE
16 | #
17 | # Foreign Keys
18 | #
19 | # fk_rails_... (account_id => accounts.id)
20 | #
21 | FactoryBot.define do
22 | factory :digital_ocean_droplet do
23 |
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/factories/notion_account_configurations.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: notion_account_configurations
4 | #
5 | # id :uuid not null, primary key
6 | # access_token :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | #
10 | # Indexes
11 | #
12 | # index_notion_account_configurations_on_access_token (access_token)
13 | #
14 | FactoryBot.define do
15 | factory :notion_account_configuration do
16 |
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/spec/factories/notion_databases.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: notion_databases
4 | #
5 | # id :uuid not null, primary key
6 | # created_at :datetime not null
7 | # updated_at :datetime not null
8 | # account_id :uuid
9 | # database_id :string not null
10 | #
11 | # Indexes
12 | #
13 | # index_notion_databases_on_account_id (account_id)
14 | # unique_database_per_account (account_id,database_id) UNIQUE
15 | #
16 | # Foreign Keys
17 | #
18 | # fk_rails_... (account_id => accounts.id)
19 | #
20 | FactoryBot.define do
21 | factory :notion_database do
22 |
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/spec/factories/notion_rows.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: notion_rows
4 | #
5 | # id :uuid not null, primary key
6 | # object_hash :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | # notion_database_id :uuid
10 | # row_id :string
11 | #
12 | # Indexes
13 | #
14 | # index_notion_rows_on_notion_database_id (notion_database_id)
15 | # index_notion_rows_on_notion_database_id_and_row_id (notion_database_id,row_id) UNIQUE
16 | #
17 | # Foreign Keys
18 | #
19 | # fk_rails_... (notion_database_id => notion_databases.id) ON DELETE => cascade
20 | #
21 | FactoryBot.define do
22 | factory :notion_row do
23 |
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/factories/profiles.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: profiles
4 | #
5 | # id :uuid not null, primary key
6 | # firstname :string not null
7 | # lastname :string not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | # user_id :uuid
11 | #
12 | # Indexes
13 | #
14 | # index_profiles_on_user_id (user_id)
15 | #
16 | # Foreign Keys
17 | #
18 | # fk_rails_... (user_id => users.id)
19 | #
20 | FactoryBot.define do
21 | factory :profile do
22 |
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/spec/factories/render_account_configurations.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: render_account_configurations
4 | #
5 | # id :uuid not null, primary key
6 | # api_key :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | #
10 | # Indexes
11 | #
12 | # index_render_account_configurations_on_api_key (api_key) UNIQUE
13 | #
14 | FactoryBot.define do
15 | factory :render_account_configuration do
16 |
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/spec/factories/render_accounts.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: render_accounts
4 | #
5 | # id :uuid not null, primary key
6 | # api_key :string not null
7 | # last_poll_time :datetime not null
8 | # name :string not null
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | #
12 | # Indexes
13 | #
14 | # index_render_accounts_on_name_and_api_key (name,api_key) UNIQUE
15 | #
16 | FactoryBot.define do
17 | factory :render_account do
18 |
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/spec/factories/render_deployments.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: render_deployments
4 | #
5 | # id :uuid not null, primary key
6 | # object_hash :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | # deployment_id :string not null
10 | # render_service_id :uuid
11 | #
12 | # Indexes
13 | #
14 | # index_render_deployments_on_deployment_id (deployment_id) UNIQUE
15 | # index_render_deployments_on_render_service_id (render_service_id)
16 | #
17 | # Foreign Keys
18 | #
19 | # fk_rails_... (render_service_id => render_services.id) ON DELETE => cascade
20 | #
21 | FactoryBot.define do
22 | factory :render_deployment do
23 |
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/factories/render_services.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: render_services
4 | #
5 | # id :uuid not null, primary key
6 | # object_hash :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | # account_id :uuid
10 | # service_id :string not null
11 | #
12 | # Indexes
13 | #
14 | # index_render_services_on_account_id (account_id)
15 | # index_render_services_on_service_id (service_id) UNIQUE
16 | #
17 | # Foreign Keys
18 | #
19 | # fk_rails_... (account_id => accounts.id)
20 | #
21 | FactoryBot.define do
22 | factory :render_service do
23 |
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/factories/services.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: services
4 | #
5 | # id :uuid not null, primary key
6 | # name :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | #
10 | # Indexes
11 | #
12 | # index_services_on_name (name) UNIQUE
13 | #
14 | FactoryBot.define do
15 | factory :service do
16 |
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/spec/factories/users.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: users
4 | #
5 | # id :uuid not null, primary key
6 | # email :string default(""), not null
7 | # encrypted_password :string default(""), not null
8 | # provider :string
9 | # remember_created_at :datetime
10 | # reset_password_sent_at :datetime
11 | # reset_password_token :string
12 | # created_at :datetime not null
13 | # updated_at :datetime not null
14 | #
15 | # Indexes
16 | #
17 | # index_users_on_email (email) UNIQUE
18 | # index_users_on_reset_password_token (reset_password_token) UNIQUE
19 | #
20 | FactoryBot.define do
21 | factory :user do
22 |
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/spec/factories/webhook_subscriptions.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: webhook_subscriptions
4 | #
5 | # id :uuid not null, primary key
6 | # created_at :datetime not null
7 | # updated_at :datetime not null
8 | # convoy_endpoint_id :string not null
9 | # convoy_subscription_id :string not null
10 | #
11 | FactoryBot.define do
12 | factory :webhook_subscription do
13 |
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/spec/factories/webhooks.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: webhooks
4 | #
5 | # id :uuid not null, primary key
6 | # event_type :string not null
7 | # payload :json not null
8 | # status :integer default("pending")
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | # account_id :uuid
12 | #
13 | # Indexes
14 | #
15 | # index_webhooks_on_account_id (account_id)
16 | #
17 | # Foreign Keys
18 | #
19 | # fk_rails_... (account_id => accounts.id)
20 | #
21 | FactoryBot.define do
22 | factory :webhook do
23 |
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/spec/factories/whitelisted_jwts.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: whitelisted_jwts
4 | #
5 | # id :uuid not null, primary key
6 | # aud :string
7 | # exp :datetime not null
8 | # jti :string not null
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | # user_id :uuid
12 | #
13 | # Indexes
14 | #
15 | # index_whitelisted_jwts_on_jti (jti) UNIQUE
16 | # index_whitelisted_jwts_on_user_id (user_id)
17 | #
18 | # Foreign Keys
19 | #
20 | # fk_rails_... (user_id => users.id)
21 | #
22 | FactoryBot.define do
23 | factory :whitelisted_jwt do
24 |
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/spec/helpers/api/v1/accounts_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | # Specs in this file have access to a helper object that includes
4 | # the Api::V1::AccountsHelper. For example:
5 | #
6 | # describe Api::V1::AccountsHelper do
7 | # describe "string concat" do
8 | # it "concats two strings with spaces" do
9 | # expect(helper.concat_strings("this","that")).to eq("this that")
10 | # end
11 | # end
12 | # end
13 | RSpec.describe Api::V1::AccountsHelper, type: :helper do
14 | pending "add some examples to (or delete) #{__FILE__}"
15 | end
16 |
--------------------------------------------------------------------------------
/spec/helpers/authentication_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | # Specs in this file have access to a helper object that includes
4 | # the AuthenticationHelper. For example:
5 | #
6 | # describe AuthenticationHelper do
7 | # describe "string concat" do
8 | # it "concats two strings with spaces" do
9 | # expect(helper.concat_strings("this","that")).to eq("this that")
10 | # end
11 | # end
12 | # end
13 | RSpec.describe AuthenticationHelper, type: :helper do
14 | pending "add some examples to (or delete) #{__FILE__}"
15 | end
16 |
--------------------------------------------------------------------------------
/spec/jobs/poll_digital_ocean_resources_job_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe PollDigitalOceanResourcesJob, type: :job do
4 | pending "add some examples to (or delete) #{__FILE__}"
5 | end
6 |
--------------------------------------------------------------------------------
/spec/jobs/poll_notion_resources_job_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe PollNotionResourcesJob, type: :job do
4 | pending "add some examples to (or delete) #{__FILE__}"
5 | end
6 |
--------------------------------------------------------------------------------
/spec/jobs/poll_render_job_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe PollRenderJob, type: :job do
4 | pending "add some examples to (or delete) #{__FILE__}"
5 | end
6 |
--------------------------------------------------------------------------------
/spec/jobs/poll_render_resources_job_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe PollRenderResourcesJob, type: :job do
4 | pending "add some examples to (or delete) #{__FILE__}"
5 | end
6 |
--------------------------------------------------------------------------------
/spec/jobs/push_webhook_to_convoy_job_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe PushWebhookToConvoyJob, type: :job do
4 | pending "add some examples to (or delete) #{__FILE__}"
5 | end
6 |
--------------------------------------------------------------------------------
/spec/jobs/send_webhooks_to_convoy_job_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe SendWebhooksToConvoyJob, type: :job do
4 | pending "add some examples to (or delete) #{__FILE__}"
5 | end
6 |
--------------------------------------------------------------------------------
/spec/models/digital_ocean_account_configuration_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: digital_ocean_account_configurations
4 | #
5 | # id :uuid not null, primary key
6 | # access_token :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | #
10 | require 'rails_helper'
11 |
12 | RSpec.describe DigitalOceanAccountConfiguration, type: :model do
13 | pending "add some examples to (or delete) #{__FILE__}"
14 | end
15 |
--------------------------------------------------------------------------------
/spec/models/digital_ocean_droplet_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: digital_ocean_droplets
4 | #
5 | # id :uuid not null, primary key
6 | # object_hash :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | # account_id :uuid
10 | # droplet_id :string not null
11 | #
12 | # Indexes
13 | #
14 | # index_digital_ocean_droplets_on_account_id (account_id)
15 | # index_digital_ocean_droplets_on_account_id_and_droplet_id (account_id,droplet_id) UNIQUE
16 | #
17 | # Foreign Keys
18 | #
19 | # fk_rails_... (account_id => accounts.id)
20 | #
21 | require 'rails_helper'
22 |
23 | RSpec.describe DigitalOceanDroplet, type: :model do
24 | pending "add some examples to (or delete) #{__FILE__}"
25 | end
26 |
--------------------------------------------------------------------------------
/spec/models/notion_account_configuration_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: notion_account_configurations
4 | #
5 | # id :uuid not null, primary key
6 | # access_token :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | #
10 | # Indexes
11 | #
12 | # index_notion_account_configurations_on_access_token (access_token)
13 | #
14 | require 'rails_helper'
15 |
16 | RSpec.describe NotionAccountConfiguration, type: :model do
17 | pending "add some examples to (or delete) #{__FILE__}"
18 | end
19 |
--------------------------------------------------------------------------------
/spec/models/notion_database_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: notion_databases
4 | #
5 | # id :uuid not null, primary key
6 | # created_at :datetime not null
7 | # updated_at :datetime not null
8 | # account_id :uuid
9 | # database_id :string not null
10 | #
11 | # Indexes
12 | #
13 | # index_notion_databases_on_account_id (account_id)
14 | # unique_database_per_account (account_id,database_id) UNIQUE
15 | #
16 | # Foreign Keys
17 | #
18 | # fk_rails_... (account_id => accounts.id)
19 | #
20 | require 'rails_helper'
21 |
22 | RSpec.describe NotionDatabase, type: :model do
23 | pending "add some examples to (or delete) #{__FILE__}"
24 | end
25 |
--------------------------------------------------------------------------------
/spec/models/notion_row_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: notion_rows
4 | #
5 | # id :uuid not null, primary key
6 | # object_hash :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | # notion_database_id :uuid
10 | # row_id :string
11 | #
12 | # Indexes
13 | #
14 | # index_notion_rows_on_notion_database_id (notion_database_id)
15 | # index_notion_rows_on_notion_database_id_and_row_id (notion_database_id,row_id) UNIQUE
16 | #
17 | # Foreign Keys
18 | #
19 | # fk_rails_... (notion_database_id => notion_databases.id) ON DELETE => cascade
20 | #
21 | require 'rails_helper'
22 |
23 | RSpec.describe NotionRow, type: :model do
24 | pending "add some examples to (or delete) #{__FILE__}"
25 | end
26 |
--------------------------------------------------------------------------------
/spec/models/profile_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: profiles
4 | #
5 | # id :uuid not null, primary key
6 | # firstname :string not null
7 | # lastname :string not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | # user_id :uuid
11 | #
12 | # Indexes
13 | #
14 | # index_profiles_on_user_id (user_id)
15 | #
16 | # Foreign Keys
17 | #
18 | # fk_rails_... (user_id => users.id)
19 | #
20 | require 'rails_helper'
21 |
22 | RSpec.describe Profile, type: :model do
23 | pending "add some examples to (or delete) #{__FILE__}"
24 | end
25 |
--------------------------------------------------------------------------------
/spec/models/render_account_configuration_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: render_account_configurations
4 | #
5 | # id :uuid not null, primary key
6 | # api_key :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | #
10 | # Indexes
11 | #
12 | # index_render_account_configurations_on_api_key (api_key) UNIQUE
13 | #
14 | require 'rails_helper'
15 |
16 | RSpec.describe RenderAccountConfiguration, type: :model do
17 | pending "add some examples to (or delete) #{__FILE__}"
18 | end
19 |
--------------------------------------------------------------------------------
/spec/models/render_account_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: render_accounts
4 | #
5 | # id :uuid not null, primary key
6 | # api_key :string not null
7 | # last_poll_time :datetime not null
8 | # name :string not null
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | #
12 | # Indexes
13 | #
14 | # index_render_accounts_on_name_and_api_key (name,api_key) UNIQUE
15 | #
16 | require 'rails_helper'
17 |
18 | RSpec.describe RenderAccount, type: :model do
19 | pending "add some examples to (or delete) #{__FILE__}"
20 | end
21 |
--------------------------------------------------------------------------------
/spec/models/render_deployment_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: render_deployments
4 | #
5 | # id :uuid not null, primary key
6 | # object_hash :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | # deployment_id :string not null
10 | # render_service_id :uuid
11 | #
12 | # Indexes
13 | #
14 | # index_render_deployments_on_deployment_id (deployment_id) UNIQUE
15 | # index_render_deployments_on_render_service_id (render_service_id)
16 | #
17 | # Foreign Keys
18 | #
19 | # fk_rails_... (render_service_id => render_services.id) ON DELETE => cascade
20 | #
21 | require 'rails_helper'
22 |
23 | RSpec.describe RenderDeployment, type: :model do
24 | pending "add some examples to (or delete) #{__FILE__}"
25 | end
26 |
--------------------------------------------------------------------------------
/spec/models/render_service_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: render_services
4 | #
5 | # id :uuid not null, primary key
6 | # object_hash :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | # account_id :uuid
10 | # service_id :string not null
11 | #
12 | # Indexes
13 | #
14 | # index_render_services_on_account_id (account_id)
15 | # index_render_services_on_service_id (service_id) UNIQUE
16 | #
17 | # Foreign Keys
18 | #
19 | # fk_rails_... (account_id => accounts.id)
20 | #
21 | require 'rails_helper'
22 |
23 | RSpec.describe RenderService, type: :model do
24 | pending "add some examples to (or delete) #{__FILE__}"
25 | end
26 |
--------------------------------------------------------------------------------
/spec/models/service_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: services
4 | #
5 | # id :uuid not null, primary key
6 | # name :string not null
7 | # created_at :datetime not null
8 | # updated_at :datetime not null
9 | #
10 | # Indexes
11 | #
12 | # index_services_on_name (name) UNIQUE
13 | #
14 | require 'rails_helper'
15 |
16 | RSpec.describe Service, type: :model do
17 | pending "add some examples to (or delete) #{__FILE__}"
18 | end
19 |
--------------------------------------------------------------------------------
/spec/models/user_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: users
4 | #
5 | # id :uuid not null, primary key
6 | # email :string default(""), not null
7 | # encrypted_password :string default(""), not null
8 | # provider :string
9 | # remember_created_at :datetime
10 | # reset_password_sent_at :datetime
11 | # reset_password_token :string
12 | # created_at :datetime not null
13 | # updated_at :datetime not null
14 | #
15 | # Indexes
16 | #
17 | # index_users_on_email (email) UNIQUE
18 | # index_users_on_reset_password_token (reset_password_token) UNIQUE
19 | #
20 | require 'rails_helper'
21 |
22 | RSpec.describe User, type: :model do
23 | pending "add some examples to (or delete) #{__FILE__}"
24 | end
25 |
--------------------------------------------------------------------------------
/spec/models/webhook_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: webhooks
4 | #
5 | # id :uuid not null, primary key
6 | # event_type :string not null
7 | # payload :json not null
8 | # status :integer default("pending")
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | # account_id :uuid
12 | #
13 | # Indexes
14 | #
15 | # index_webhooks_on_account_id (account_id)
16 | #
17 | # Foreign Keys
18 | #
19 | # fk_rails_... (account_id => accounts.id)
20 | #
21 | require 'rails_helper'
22 |
23 | RSpec.describe Webhook, type: :model do
24 | pending "add some examples to (or delete) #{__FILE__}"
25 | end
26 |
--------------------------------------------------------------------------------
/spec/models/webhook_subscription_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: webhook_subscriptions
4 | #
5 | # id :uuid not null, primary key
6 | # created_at :datetime not null
7 | # updated_at :datetime not null
8 | # convoy_endpoint_id :string not null
9 | # convoy_subscription_id :string not null
10 | #
11 | require 'rails_helper'
12 |
13 | RSpec.describe WebhookSubscription, type: :model do
14 | pending "add some examples to (or delete) #{__FILE__}"
15 | end
16 |
--------------------------------------------------------------------------------
/spec/models/whitelisted_jwt_spec.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: whitelisted_jwts
4 | #
5 | # id :uuid not null, primary key
6 | # aud :string
7 | # exp :datetime not null
8 | # jti :string not null
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | # user_id :uuid
12 | #
13 | # Indexes
14 | #
15 | # index_whitelisted_jwts_on_jti (jti) UNIQUE
16 | # index_whitelisted_jwts_on_user_id (user_id)
17 | #
18 | # Foreign Keys
19 | #
20 | # fk_rails_... (user_id => users.id)
21 | #
22 | require 'rails_helper'
23 |
24 | RSpec.describe WhitelistedJwt, type: :model do
25 | pending "add some examples to (or delete) #{__FILE__}"
26 | end
27 |
--------------------------------------------------------------------------------
/spec/requests/api/v1/accounts_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe "Api::V1::Accounts", type: :request do
4 | describe "GET /index" do
5 | pending "add some examples (or delete) #{__FILE__}"
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/requests/authentication_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe "Authentications", type: :request do
4 | describe "GET /index" do
5 | pending "add some examples (or delete) #{__FILE__}"
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/storage/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/storage/.keep
--------------------------------------------------------------------------------
/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/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/test/controllers/.keep
--------------------------------------------------------------------------------
/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/test/fixtures/files/.keep
--------------------------------------------------------------------------------
/test/helpers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/test/helpers/.keep
--------------------------------------------------------------------------------
/test/integration/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/test/integration/.keep
--------------------------------------------------------------------------------
/test/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/test/mailers/.keep
--------------------------------------------------------------------------------
/test/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/test/models/.keep
--------------------------------------------------------------------------------
/test/system/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/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/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/tmp/.keep
--------------------------------------------------------------------------------
/vendor/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/vendor/.keep
--------------------------------------------------------------------------------
/vendor/javascript/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frain-dev/nohooks-backend/9ce1d17ce260468cd024565f02f3ec5eaee9bf4f/vendor/javascript/.keep
--------------------------------------------------------------------------------