diff --git a/.github/workflows/release_please.yml b/.github/workflows/release_please.yml new file mode 100644 index 0000000..e1c4864 --- /dev/null +++ b/.github/workflows/release_please.yml @@ -0,0 +1,30 @@ +on: + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + id-token: write + +name: release-please + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + id: release + with: + token: ${{ secrets.RELEASE_PLEASE_TOKEN }} + config-file: .release-please-config.json + - uses: actions/checkout@v4 + if: ${{ steps.release.outputs.release_created }} + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + if: ${{ steps.release.outputs.release_created }} + - uses: rubygems/release-gem@v1 + if: ${{ steps.release.outputs.release_created }} diff --git a/.github/workflows/rubyonrails.yml b/.github/workflows/rubyonrails.yml new file mode 100644 index 0000000..f57e742 --- /dev/null +++ b/.github/workflows/rubyonrails.yml @@ -0,0 +1,49 @@ +# This workflow uses actions that are not certified by GitHub. They are +# provided by a third-party and are governed by separate terms of service, +# privacy policy, and support documentation. +# +# This workflow will install a prebuilt Ruby version, install dependencies, and +# run tests and linters. +name: "Ruby on Rails CI" +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] +jobs: + test: + strategy: + fail-fast: false + matrix: + gemfile: [rails-7.0, rails-7.1, rails-7.2, rails-8.0] + runs-on: ubuntu-latest + env: + RAILS_ENV: test + BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install Ruby and gems + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + - name: Run tests + run: bin/rails t + + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Install Ruby and gems + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + - name: Install Security Scan Gems + run: gem install bundler-audit brakeman rubocop + - name: Security audit dependencies + run: bundler-audit --update + - name: Security audit application code + run: brakeman -q -w2 + - name: Lint Ruby files + run: bundle exec rubocop --parallel diff --git a/.rubocop.yml b/.rubocop.yml index a84431a..b27a165 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,14 +1,13 @@ -require: rubocop-rails - - AllCops: - TargetRubyVersion: 2.7 + TargetRubyVersion: NewCops: enable Exclude: - 'db/**/*' - 'config/**/*' - 'bin/{rails,rake}' - 'test/**/*' + - "gemfiles/*" + - "vendor/**/*" Style/FrozenStringLiteralComment: Enabled: false diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..4d9d11c --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.4.2 diff --git a/Gemfile b/Gemfile index 3bba991..d41f393 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,8 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } # Specify your gem's dependencies in rails_url_shortener.gemspec. gemspec -gem 'rails', '>= 7.0.2.3' +gem 'rails', '~> 7.2' + gem 'sqlite3' gem 'sprockets-rails' @@ -21,10 +22,8 @@ group :test do gem 'vcr' end -gem 'rubocop', require: false -gem 'rubocop-minitest', require: false -gem 'rubocop-rails', require: false - -gem 'minitest-cc' +group :development, :test do + gem 'rubocop' -gem 'annotate' + gem 'minitest-cc' +end diff --git a/Gemfile.lock b/Gemfile.lock index 2684ad3..371e33d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,7 +3,6 @@ PATH specs: rails_url_shortener (0.2.10) browser (>= 5.3.0) - bundler (>= 1.15.0) http (>= 5.1.0) GEM @@ -82,9 +81,6 @@ GEM tzinfo (~> 2.0, >= 2.0.5) addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) - annotate (3.2.0) - activerecord (>= 3.2, < 8.0) - rake (>= 10.4, < 14.0) ast (2.4.2) base64 (0.2.0) benchmark (0.4.0) @@ -144,7 +140,7 @@ GEM net-smtp marcel (1.0.4) mini_mime (1.1.5) - minitest (5.25.4) + minitest (5.25.5) minitest-cc (1.0.0) net-imap (0.5.6) date @@ -156,7 +152,7 @@ GEM net-smtp (0.5.1) net-protocol nio4r (2.7.4) - nokogiri (1.18.3-x86_64-linux-gnu) + nokogiri (1.18.4-x86_64-linux-gnu) racc (~> 1.4) parallel (1.26.3) parser (3.3.7.1) @@ -215,7 +211,7 @@ GEM reline (0.6.0) io-console (~> 0.5) rexml (3.4.1) - rubocop (1.73.2) + rubocop (1.74.0) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -226,18 +222,8 @@ GEM rubocop-ast (>= 1.38.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.38.1) + rubocop-ast (1.39.0) parser (>= 3.3.1.0) - rubocop-minitest (0.37.1) - lint_roller (~> 1.1) - rubocop (>= 1.72.1, < 2.0) - rubocop-ast (>= 1.38.0, < 2.0) - rubocop-rails (2.30.3) - activesupport (>= 4.2.0) - lint_roller (~> 1.1) - rack (>= 1.1) - rubocop (>= 1.72.1, < 2.0) - rubocop-ast (>= 1.38.0, < 2.0) ruby-progressbar (1.13.0) securerandom (0.4.1) sprockets (4.2.1) @@ -273,15 +259,12 @@ PLATFORMS x86_64-linux DEPENDENCIES - annotate byebug faker minitest-cc - rails (>= 7.0.2.3) + rails (~> 7.2) rails_url_shortener! rubocop - rubocop-minitest - rubocop-rails sprockets-rails sqlite3 vcr diff --git a/README.md b/README.md index 102f7ef..e21cce2 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,53 @@ # RailsUrlShortener -RailsUrlShortener is a small rails engine that provide your app with short URLs functionalities. Like a Bitly on your app. By default, RailsUrlShortener save the visits to your link for future interesting things that you may want to do. +RailsUrlShortener is a small Rails engine that provides your app with short URL functionality and IP logging capabilities - like having your own Bitly service. By default, RailsUrlShortener saves all visits to your links for future analysis or other interesting uses. -Why give your data to a third party app if you can do it by yourself? +Why give your data to a third-party app when you can manage it yourself? You can see a **demo project** of what you can do with this engine [HERE](https://paso.fly.dev/). -## Key features +## Key Features -A few of the things you can do with RailsUrlShortener: +Here are some of the things you can do with RailsUrlShortener: -* Generate unique keys for links. -* Provide a method controller that find, save request information and does a 301 redirect to the original url. -* The short links can be associated with a model in your app. -* Save interesting things like browser, system and ip data of the 'un-shortened' request. -* Temporal short links using the expires_at option. -* Get IP data from third part service. +* Generate unique keys for links +* Provide a controller method that finds, saves request information, and performs a 301 redirect to the original URL +* Associate short links with models in your app +* Save browser, system, and IP data from each request +* Create temporary short links using the expires_at option +* Get IP data from a third-party service ## Installation -Add this line to your application's Gemfile: +Follow these steps to install and configure RailsUrlShortener: + +1. Add this line to your application's Gemfile: ```ruby gem "rails_url_shortener" ``` -Or install it yourself as: - -```bash -gem install rails_url_shortener -``` - -Then execute: +2. Install the gem by running: ```bash -bundle +bundle install ``` -And finally install & run the migrations on your project and migrate: +3. Install and run the migrations: ```bash bin/rails rails_url_shortener:install:migrations db:migrate ``` -For the configurations generate the initializer whith this: +4. Generate the initializer for configuration: ```bash -rails generate RailsUrlShortener:initializer +rails generate rails_url_shortener ``` -**Here is important to configure the host at least if your are not running your app in localhost** - ## Usage -### 1. Mount the engine +1. Mount the engine Mount the engine on your app adding the next code on your config/routes.rb: @@ -64,7 +58,7 @@ mount RailsUrlShortener::Engine, at: "/" ``` -### 2. Generate the short link +2. Generate the short link And generate the short links like you want: @@ -80,7 +74,7 @@ short_url("https://www.github.com/a-chacon/rails-url-shortener") RailsUrlShortener::Url.generate("https://www.github.com/a-chacon/rails-url-shortener") ``` -### 3. Share the short link +3. Share the short link **Then share the short link to your users or wherever you want.** @@ -94,43 +88,44 @@ short_url(url, owner: nil, key: nil, expires_at: nil, category: nil, url_options Where: -* **url**: Long url for short. -* **owner**: Is a model of your app. You can relate an url whatever you want in your app. -* **key**: Is a custom key that you want to set up. -* **expires_at**: Is a datetime for expiration, after this the redirection doesn't work. -* **category**: Tag that you want for that link. -* **url_options**: Options for the url_for generator. Ex: subdomain or protocol. +* **url**: The long URL to be shortened +* **owner**: A model from your app to associate with the URL +* **key**: A custom key for the short URL (optional) +* **expires_at**: Expiration datetime (after which the redirect won't work) +* **category**: A tag for categorizing the link +* **url_options**: Options for the URL generator (e.g., subdomain or protocol) -And the same for the generate model method except for url_options: +The `generate` model method accepts the same parameters except for `url_options`: ```ruby RailsUrlShortener::Url.generate(url, owner: nil, key: nil, expires_at: nil, category: nil) ``` -### Data saved +### Data Collection -By default, this engine save all request made on your short url, you can use that data for some analytics or simple IP logger. So for get the data in a controller or do wherever you want, you can use the Visit model related to an Url: +By default, the engine saves all requests made to your short URLs. You can use this data for analytics or IP logging. To access the data: -```ruby -RailsUrlShortener::Url.find_by_key("key").visits # all visits +1. Get visits for a specific URL: +```ruby +RailsUrlShortener::Url.find_by_key("key").visits ``` -Or using the model class: +2. Get all visits: ```ruby -RailsUrlShortener::Visit.all # all in database +RailsUrlShortener::Visit.all ``` -And a Visit is related to a Ipgeo model that contain information about the ip, so you can view this using the active record relation: +Each Visit is associated with an Ipgeo model that contains information about the IP address: ```ruby -RailsUrlShortener::Visit.first.ipgeo # Ipgeo object that contain information of the ip +RailsUrlShortener::Visit.first.ipgeo ``` -### Ip data +### IP Data Collection -When a Visit record is created, a job is enqueue for get Ip data from [this](https://ip-api.com/) service and create the Ipgeo record. It is integrated to the free endpoint, so if you think that you have more than 45 different IPS querying in a minute to your app, we need to think in a new solution. +When a Visit record is created, a background job is enqueued to fetch IP data from the [ip-api.com](https://ip-api.com/) service and create an Ipgeo record. This uses the free endpoint, which has a limit of 45 different IPs per minute. If you expect higher traffic, you'll need to implement an alternative solution. ## Contributing diff --git a/Rakefile b/Rakefile index 9a6aeee..d7fbe48 100644 --- a/Rakefile +++ b/Rakefile @@ -17,10 +17,4 @@ Rake::TestTask.new(:test) do |t| t.test_files = FileList['test/**/*test.rb'] end -require 'rubocop/rake_task' - -RuboCop::RakeTask.new do |task| - task.requires << 'rubocop-minitest' -end - -task default: %i[test rubocop] +task default: %i[test] diff --git a/gemfiles/rails-7.0.gemfile b/gemfiles/rails-7.0.gemfile new file mode 100644 index 0000000..c1a71d5 --- /dev/null +++ b/gemfiles/rails-7.0.gemfile @@ -0,0 +1,54 @@ +source "https://rubygems.org" +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +gem "rails", "~> 7.0.8", ">= 7.0.8.7" + +gem 'concurrent-ruby', '1.3.4' + +gem 'bigdecimal', '~> 3.1', '>= 3.1.9' + +gem 'mutex_m', '~> 0.3.0' + +gem 'drb', '~> 2.2', '>= 2.2.1' + +gem "sprockets-rails" + +gem "sqlite3", "~> 1.4" + +gem "puma", "~> 5.0" + +gem "importmap-rails" + +gem "turbo-rails" + +gem "stimulus-rails" + +gem "jbuilder" + +gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] + +gem "bootsnap", require: false + +group :development, :test do + gem "debug", platforms: %i[ mri mingw x64_mingw ] +end + +group :development do + gem "web-console" +end + +gem 'browser', '~> 6.2' + +gem 'http', '~> 5.2' + +gem 'byebug' + +gem 'faker' + +gem 'webmock' + +gem 'vcr' + +gem 'rubocop' + +gem 'minitest-cc' diff --git a/gemfiles/rails-7.1.gemfile b/gemfiles/rails-7.1.gemfile new file mode 100644 index 0000000..3182101 --- /dev/null +++ b/gemfiles/rails-7.1.gemfile @@ -0,0 +1,45 @@ +source "https://rubygems.org" + +gem "rails", "~> 7.1.5", ">= 7.1.5.1" + +gem "sprockets-rails" + +gem "sqlite3", ">= 1.4" + +gem "puma", ">= 5.0" + +gem "importmap-rails" + +gem "turbo-rails" + +gem "stimulus-rails" + +gem "jbuilder" + +gem "tzinfo-data", platforms: %i[ windows jruby ] + +gem "bootsnap", require: false + +group :development, :test do + gem "debug", platforms: %i[ mri windows ] +end + +group :development do + gem "web-console" +end + +gem 'browser', '~> 6.2' + +gem 'http', '~> 5.2' + +gem 'byebug' + +gem 'faker' + +gem 'webmock' + +gem 'vcr' + +gem 'rubocop' + +gem 'minitest-cc' diff --git a/gemfiles/rails-7.2.gemfile b/gemfiles/rails-7.2.gemfile new file mode 100644 index 0000000..165f9c0 --- /dev/null +++ b/gemfiles/rails-7.2.gemfile @@ -0,0 +1,25 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +gem 'rails', '~> 7.2.0' + +gem 'browser', '~> 6.2' + +gem 'http', '~> 5.2' + +gem 'sqlite3' + +gem 'sprockets-rails' + +gem 'byebug' + +gem 'faker' + +gem 'webmock' + +gem 'vcr' + +gem 'rubocop', require: false + +gem 'minitest-cc' + diff --git a/gemfiles/rails-8.0.gemfile b/gemfiles/rails-8.0.gemfile new file mode 100644 index 0000000..28b22f2 --- /dev/null +++ b/gemfiles/rails-8.0.gemfile @@ -0,0 +1,26 @@ +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +gem 'rails', '~> 8.0', '>= 8.0.2' + +gem 'browser', '~> 6.2' + +gem 'http', '~> 5.2' + +gem 'sqlite3' + +gem 'sprockets-rails' + +gem 'byebug' + +gem 'faker' + +gem 'webmock' + +gem 'vcr' + +gem 'rubocop', require: false + +gem 'minitest-cc' + + diff --git a/lib/rails_url_shortener.rb b/lib/rails_url_shortener.rb index 5935a11..22a6c55 100644 --- a/lib/rails_url_shortener.rb +++ b/lib/rails_url_shortener.rb @@ -3,6 +3,7 @@ require 'rails_url_shortener/version' require 'rails_url_shortener/engine' require 'rails_url_shortener/model' +require_relative '../app/helpers/rails_url_shortener/urls_helper' module RailsUrlShortener ## diff --git a/lib/tasks/auto_annotate_models.rake b/lib/tasks/auto_annotate_models.rake deleted file mode 100644 index e96283e..0000000 --- a/lib/tasks/auto_annotate_models.rake +++ /dev/null @@ -1,59 +0,0 @@ -# NOTE: only doing this in development as some production environments (Heroku) -# NOTE: are sensitive to local FS writes, and besides -- it's just not proper -# NOTE: to have a dev-mode tool do its thing in production. -if Rails.env.development? - require 'annotate' - task :set_annotation_options do - # You can override any of these by setting an environment variable of the - # same name. - Annotate.set_defaults( - 'active_admin' => 'false', - 'additional_file_patterns' => [], - 'routes' => 'false', - 'models' => 'true', - 'position_in_routes' => 'before', - 'position_in_class' => 'before', - 'position_in_test' => 'before', - 'position_in_fixture' => 'before', - 'position_in_factory' => 'before', - 'position_in_serializer' => 'before', - 'show_foreign_keys' => 'true', - 'show_complete_foreign_keys' => 'false', - 'show_indexes' => 'true', - 'simple_indexes' => 'false', - 'model_dir' => 'app/models', - 'root_dir' => '', - 'include_version' => 'false', - 'require' => '', - 'exclude_tests' => 'false', - 'exclude_fixtures' => 'false', - 'exclude_factories' => 'false', - 'exclude_serializers' => 'false', - 'exclude_scaffolds' => 'true', - 'exclude_controllers' => 'true', - 'exclude_helpers' => 'true', - 'exclude_sti_subclasses' => 'false', - 'ignore_model_sub_dir' => 'false', - 'ignore_columns' => nil, - 'ignore_routes' => nil, - 'ignore_unknown_models' => 'false', - 'hide_limit_column_types' => 'integer,bigint,boolean', - 'hide_default_column_types' => 'json,jsonb,hstore', - 'skip_on_db_migrate' => 'false', - 'format_bare' => 'true', - 'format_rdoc' => 'false', - 'format_yard' => 'false', - 'format_markdown' => 'false', - 'sort' => 'false', - 'force' => 'false', - 'frozen' => 'false', - 'classified_sort' => 'true', - 'trace' => 'false', - 'wrapper_open' => nil, - 'wrapper_close' => nil, - 'with_comment' => 'true' - ) - end - - Annotate.load_tasks -end diff --git a/rails_url_shortener.gemspec b/rails_url_shortener.gemspec index 5fec88a..b5f4d5e 100644 --- a/rails_url_shortener.gemspec +++ b/rails_url_shortener.gemspec @@ -11,10 +11,14 @@ Gem::Specification.new do |spec| spec.homepage = 'https://www.github.com/a-chacon/rails-url-shortener' spec.summary = 'Rails url shortener engine.' - spec.description = "RailsUrlShortener is a lightweight Rails engine that enables easy creation and management of short URLs within your project. Similar to bitly.com, it condenses long links into short, user-friendly addresses. Enhance your app's functionality with this simple yet powerful URL shortening solution." + spec.description = <<~DESC + RailsUrlShortener is a lightweight Rails engine that enables easy creation and management of short URLs within your project.#{' '} + Similar to bitly.com, it condenses long links into short, user-friendly addresses.#{' '} + Enhance your app's functionality with this simple yet powerful URL shortening solution. + DESC spec.license = 'GPL-3.0' - spec.required_ruby_version = '>= 2.7.0' + spec.required_ruby_version = '>= 3.1' spec.required_rubygems_version = '>= 1.8.11' spec.metadata['homepage_uri'] = spec.homepage @@ -32,6 +36,5 @@ Gem::Specification.new do |spec| end spec.add_dependency 'browser', '>= 5.3.0' - spec.add_dependency 'bundler', '>= 1.15.0' spec.add_dependency 'http', '>= 5.1.0' end diff --git a/test/test_helper.rb b/test/test_helper.rb index 0d87755..38242a5 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -11,7 +11,12 @@ require 'rails/test_help' # Load fixtures from the engine -if ActiveSupport::TestCase.respond_to?(:fixture_path=) +if ActiveSupport::TestCase.respond_to?(:fixture_paths=) + ActiveSupport::TestCase.fixture_paths = [File.expand_path('fixtures', __dir__)] + ActionDispatch::IntegrationTest.fixture_paths = ActiveSupport::TestCase.fixture_paths + ActiveSupport::TestCase.file_fixture_path = File.expand_path('fixtures', __dir__) + '/files' + ActiveSupport::TestCase.fixtures :all +elsif ActiveSupport::TestCase.respond_to?(:fixture_path=) ActiveSupport::TestCase.fixture_path = File.expand_path('fixtures', __dir__) ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + '/files'