- <%= yield %> +
+
+ <% flash.each do |key, message| %> +
<%= message %>
+ <% end %> + +
+ <%= yield %> +
diff --git a/app/views/layouts/bookmarklet.html.erb b/app/views/layouts/bookmarklet.html.erb index 31c89fd..1b760bc 100644 --- a/app/views/layouts/bookmarklet.html.erb +++ b/app/views/layouts/bookmarklet.html.erb @@ -6,76 +6,11 @@ <%= csrf_meta_tags %> <%= csp_meta_tag %> - <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> + <%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %> <%= javascript_importmap_tags %> \ No newline at end of file + diff --git a/bin/dev b/bin/dev index 5f91c20..ad72c7d 100755 --- a/bin/dev +++ b/bin/dev @@ -1,2 +1,16 @@ -#!/usr/bin/env ruby -exec "./bin/rails", "server", *ARGV +#!/usr/bin/env sh + +if ! gem list foreman -i --silent; then + echo "Installing foreman..." + gem install foreman +fi + +# Default to port 3000 if not specified +export PORT="${PORT:-3000}" + +# Let the debug gem allow remote connections, +# but avoid loading until `debugger` is called +export RUBY_DEBUG_OPEN="true" +export RUBY_DEBUG_LAZY="true" + +exec foreman start -f Procfile.dev "$@" diff --git a/config/application.rb b/config/application.rb index 3043220..a66f078 100644 --- a/config/application.rb +++ b/config/application.rb @@ -23,5 +23,8 @@ class Application < Rails::Application # # config.time_zone = "Central Time (US & Canada)" # config.eager_load_paths << Rails.root.join("extras") + + # Add Tailwind CSS builds directory to asset paths + config.assets.paths << Rails.root.join("app/assets/builds") end end diff --git a/config/database.yml b/config/database.yml index 2f0fc61..539f9d1 100644 --- a/config/database.yml +++ b/config/database.yml @@ -22,6 +22,7 @@ default: &default development: <<: *default + url: <%= ENV['DEVELOPMENT_DATABASE_URL'] %> database: pinstr_development # The specified database role being used to connect to PostgreSQL. @@ -56,6 +57,7 @@ development: # Do not set this db to the same as development or production. test: <<: *default + url: <%= ENV['TEST_DATABASE_URL'] %> database: pinstr_test # As with config/credentials.yml, you never want to store sensitive information, @@ -81,9 +83,10 @@ test: production: primary: &primary_production <<: *default + host: <%= ENV["DB_HOST"] %> database: pinstr_production username: pinstr - password: <%= ENV["PINSTR_DATABASE_PASSWORD"] %> + password: <%= ENV["POSTGRES_PASSWORD"] %> cache: <<: *primary_production database: pinstr_production_cache diff --git a/config/deploy.yml b/config/deploy.yml index 72214c5..7fd9bcd 100644 --- a/config/deploy.yml +++ b/config/deploy.yml @@ -1,45 +1,31 @@ -# Name of your application. Used to uniquely configure containers. service: pinstr -# Name of the container image. -image: your-user/pinstr +image: neudabei/pinstr -# Deploy to these servers. servers: web: - - 192.168.0.1 - # job: - # hosts: - # - 192.168.0.1 - # cmd: bin/jobs - -# Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server. -# Remove this section when using multiple web servers and ensure you terminate SSL at your load balancer. -# -# Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption. + - 167.71.173.177 + proxy: ssl: true - host: app.example.com + host: pinstr.co -# Credentials for your image host. registry: - # Specify the registry server, if you're not using Docker Hub - # server: registry.digitalocean.com / ghcr.io / ... - username: your-user - - # Always use an access token rather than real password when possible. + username: neudabei password: - KAMAL_REGISTRY_PASSWORD -# Inject ENV variables into containers (secrets come from .kamal/secrets). env: secret: + - KAMAL_REGISTRY_PASSWORD - RAILS_MASTER_KEY + - POSTGRES_PASSWORD + - DATABASE_URL clear: # Run the Solid Queue Supervisor inside the web server's Puma process to do jobs. # When you start using multiple servers, you should split out job processing to a dedicated machine. SOLID_QUEUE_IN_PUMA: true - + DB_HOST: 167.71.173.177 # Set number of processes dedicated to Solid Queue (default: 1) # JOB_CONCURRENCY: 3 @@ -76,7 +62,9 @@ asset_path: /rails/public/assets # Configure the image builder. builder: arch: amd64 - + secrets: + - KAMAL_REGISTRY_PASSWORD + - RAILS_MASTER_KEY # # Build image via remote server (useful for faster amd64 builds on arm64 computers) # remote: ssh://docker@docker-builder-server # @@ -92,22 +80,23 @@ builder: # user: app # Use accessory services (secrets come from .kamal/secrets). -# accessories: -# db: -# image: mysql:8.0 -# host: 192.168.0.2 -# # Change to 3306 to expose port to the world instead of just local network. -# port: "127.0.0.1:3306:3306" -# env: -# clear: -# MYSQL_ROOT_HOST: '%' -# secret: -# - MYSQL_ROOT_PASSWORD -# files: -# - config/mysql/production.cnf:/etc/mysql/my.cnf -# - db/production.sql:/docker-entrypoint-initdb.d/setup.sql -# directories: -# - data:/var/lib/mysql +accessories: + postgres: + image: postgres:15 + host: 167.71.173.177 + port: 5432 + env: + clear: + DB_HOST: "pinstr-postgres" + POSTGRES_USER: "pinstr" + secret: + - POSTGRES_PASSWORD + - DATABASE_URL + files: + - config/init.sql:/docker-entrypoint-initdb.d/setup.sql + directories: + - data:/var/lib/postgresql/data + # redis: # image: redis:7.0 # host: 192.168.0.2 diff --git a/config/environments/development.rb b/config/environments/development.rb index 4cc21c4..52991c2 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -69,4 +69,7 @@ # Apply autocorrection by RuboCop to files generated by `bin/rails generate`. # config.generators.apply_rubocop_autocorrect_after_generate! + + # Add app/assets/builds to Propshaft load path for Tailwind CSS + config.assets.paths << Rails.root.join("app/assets/builds") end diff --git a/config/init-databases.sh b/config/init-databases.sh new file mode 100644 index 0000000..18e67b8 --- /dev/null +++ b/config/init-databases.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +# Wait for PostgreSQL to be ready +until PGPASSWORD=$POSTGRES_PASSWORD psql -h localhost -U "$POSTGRES_USER" -d postgres -c '\q'; do + >&2 echo "Postgres is unavailable - sleeping" + sleep 1 +done + +# Create databases +PGPASSWORD=$POSTGRES_PASSWORD psql -v ON_ERROR_STOP=1 -h localhost -U "$POSTGRES_USER" -d postgres <<-EOSQL + SELECT 'CREATE DATABASE pinstr_production' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'pinstr_production')\gexec + SELECT 'CREATE DATABASE pinstr_production_cache' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'pinstr_production_cache')\gexec + SELECT 'CREATE DATABASE pinstr_production_queue' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'pinstr_production_queue')\gexec + SELECT 'CREATE DATABASE pinstr_production_cable' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'pinstr_production_cable')\gexec + + GRANT ALL PRIVILEGES ON DATABASE pinstr_production TO $POSTGRES_USER; + GRANT ALL PRIVILEGES ON DATABASE pinstr_production_cache TO $POSTGRES_USER; + GRANT ALL PRIVILEGES ON DATABASE pinstr_production_queue TO $POSTGRES_USER; + GRANT ALL PRIVILEGES ON DATABASE pinstr_production_cable TO $POSTGRES_USER; +EOSQL \ No newline at end of file diff --git a/config/init.sql b/config/init.sql new file mode 100644 index 0000000..4d3590c --- /dev/null +++ b/config/init.sql @@ -0,0 +1,11 @@ +-- Create all databases from postgres database +CREATE DATABASE pinstr_production; +CREATE DATABASE pinstr_production_cache; +CREATE DATABASE pinstr_production_queue; +CREATE DATABASE pinstr_production_cable; + +-- Grant all privileges to the pinstr user on all databases +GRANT ALL PRIVILEGES ON DATABASE pinstr_production TO pinstr; +GRANT ALL PRIVILEGES ON DATABASE pinstr_production_cache TO pinstr; +GRANT ALL PRIVILEGES ON DATABASE pinstr_production_queue TO pinstr; +GRANT ALL PRIVILEGES ON DATABASE pinstr_production_cable TO pinstr; diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 4873244..8ccb1cd 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -5,3 +5,6 @@ # Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path + +# Add Tailwind CSS builds directory to asset load path +Rails.application.config.assets.paths << Rails.root.join("app/assets/builds") diff --git a/config/routes.rb b/config/routes.rb index 16aa158..5556207 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -17,10 +17,10 @@ # Bookmarklet routes get "/bookmarklet", to: "bookmarklet#add", as: :bookmarklet post "/bookmarklet", to: "bookmarklet#create", as: :create_from_bookmarklet - get "/bookmarklet/instructions", to: "bookmarklet#instructions", as: :bookmarklet_instructions get "/bookmarklet/success", to: "bookmarklet#success", as: :success_bookmarklet - get "/bookmarklet/debug", to: "bookmarklet#debug", as: :debug_bookmarklet resources :sessions resources :bookmarks + resources :debug, only: :index + resources :instructions, only: :index end diff --git a/spec/services/url_service_spec.rb b/spec/services/url_service_spec.rb index be48447..c398f8d 100644 --- a/spec/services/url_service_spec.rb +++ b/spec/services/url_service_spec.rb @@ -61,6 +61,21 @@ expect(UrlService.valid?('not a url')).to be false expect(UrlService.valid?('http://')).to be false end + + it 'returns false for malformed protocol URLs' do + expect(UrlService.valid?('https://http')).to be false + expect(UrlService.valid?('http://https')).to be false + expect(UrlService.valid?('ftp://http')).to be false + expect(UrlService.valid?('https://ftp')).to be false + end + + it 'returns false for URLs with invalid host patterns' do + expect(UrlService.valid?('https://')).to be false + expect(UrlService.valid?('https://.')).to be false + expect(UrlService.valid?('https://..')).to be false + expect(UrlService.valid?('https://...')).to be false + expect(UrlService.valid?('https://com')).to be false + end end describe '.equivalent?' do