Skip to content

Commit

Permalink
Merge pull request #50 from TelosLabs/22-qr-code-generation
Browse files Browse the repository at this point in the history
Add QR Code generation and sharing
  • Loading branch information
LuigiR0jas committed Jul 30, 2024
2 parents 350e973 + aa4d473 commit e007d28
Show file tree
Hide file tree
Showing 24 changed files with 161 additions and 15 deletions.
3 changes: 3 additions & 0 deletions .database_consistency.todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ Profile:
self_ref:
MissingIndexChecker:
enabled: false
uuid:
ColumnPresenceChecker:
enabled: false
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ gem "avo", ">= 3.2.1"

# Other
gem "bootsnap", require: false
gem "draper"
gem "inline_svg"
gem "puma", ">= 5.0"
gem "ransack"
Expand Down
15 changes: 15 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ GEM
globalid (>= 0.3.6)
activemodel (7.1.3.4)
activesupport (= 7.1.3.4)
activemodel-serializers-xml (1.0.2)
activemodel (> 5.x)
activesupport (> 5.x)
builder (~> 3.1)
activerecord (7.1.3.4)
activemodel (= 7.1.3.4)
activesupport (= 7.1.3.4)
Expand Down Expand Up @@ -166,6 +170,13 @@ GEM
diff-lcs (1.5.1)
docile (1.4.0)
dotenv (3.1.2)
draper (4.0.2)
actionpack (>= 5.0)
activemodel (>= 5.0)
activemodel-serializers-xml (>= 1.0)
activesupport (>= 5.0)
request_store (>= 1.0)
ruby2_keywords
drb (2.2.1)
dry-configurable (1.2.0)
dry-core (~> 1.0, < 2)
Expand Down Expand Up @@ -375,6 +386,8 @@ GEM
regexp_parser (2.9.2)
reline (0.5.9)
io-console (~> 0.5)
request_store (1.7.0)
rack (>= 1.4)
rexml (3.3.2)
strscan
rouge (4.3.0)
Expand Down Expand Up @@ -436,6 +449,7 @@ GEM
rubocop (~> 1.61)
rubocop-rspec (~> 3, >= 3.0.1)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
ruby_parser (3.21.0)
racc (~> 1.5)
sexp_processor (~> 4.16)
Expand Down Expand Up @@ -546,6 +560,7 @@ DEPENDENCIES
database_consistency
debug
dotenv
draper
erb_lint
factory_bot_rails
faker
Expand Down
15 changes: 13 additions & 2 deletions app/controllers/profiles_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
class ProfilesController < ApplicationController
before_action :set_profile, only: :show

def show
@profile = current_profile
@profile = @profile.decorate
end

def edit
Expand All @@ -13,14 +15,23 @@ def update

if @profile.save
remove_profile_image_if_requested
redirect_to profile_path, notice: t("controllers.profiles.update.success")
redirect_to profile_path(@profile.uuid), notice: t("controllers.profiles.update.success")
else
render :edit, status: :unprocessable_entity
end
end

private

def set_profile
@profile =
if params[:uuid] == current_profile.uuid
current_profile
else
Profile.public_profiles.find_by!(uuid: params[:uuid])
end
end

def current_profile
current_user.profile || current_user.build_profile
end
Expand Down
13 changes: 13 additions & 0 deletions app/decorators/profile_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class ProfileDecorator < Draper::Decorator
delegate_all

def svg_qr_code(options = {})
RQRCode::QRCode.new(profile_url).as_svg(options)
end

private

def profile_url
Rails.application.routes.url_helpers.profile_url(object.uuid)
end
end
14 changes: 14 additions & 0 deletions app/models/profile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
# name :string
# profileable_type :string not null
# twitter_url :string
# uuid :string
# created_at :datetime not null
# updated_at :datetime not null
# profileable_id :integer not null
#
# Indexes
#
# index_profiles_on_profileable (profileable_type,profileable_id)
# index_profiles_on_uuid (uuid) UNIQUE
#
class Profile < ApplicationRecord
has_one_attached :image
Expand All @@ -29,4 +31,16 @@ class Profile < ApplicationRecord
has_one :speaker, through: :self_ref, source: :profileable, source_type: "Speaker"

belongs_to :profileable, polymorphic: true

validates :uuid, uniqueness: true, presence: true

before_validation :set_uuid

scope :public_profiles, -> { where(is_public: true) }

private

def set_uuid
self.uuid ||= SecureRandom.uuid
end
end
2 changes: 2 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class User < ApplicationRecord

after_create_commit { create_profile! }

delegate :uuid, to: :profile, allow_nil: true

def self.ransackable_attributes(_auth_object = nil)
%w[email]
end
Expand Down
4 changes: 2 additions & 2 deletions app/views/layouts/_bottom_navbar.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<%= inline_svg_tag "clock.svg", class: nav_icon_class_for([root_path]) %>
My Schedule
</a>
<a href="<%= profile_path %>" class="<%= nav_text_class_for [profile_path, edit_profile_path] %> flex flex-col items-center justify-center">
<%= inline_svg_tag "avatar_no_fill.svg", class: nav_icon_class_for([profile_path, edit_profile_path]) %>
<a href="<%= profile_path(current_user.uuid) %>" class="<%= nav_text_class_for [profile_path(current_user.uuid), edit_profile_path] %> flex flex-col items-center justify-center">
<%= inline_svg_tag "avatar_no_fill.svg", class: nav_icon_class_for([profile_path(current_user.uuid), edit_profile_path]) %>
Profile
</a>
<a href="<%= root_path %>" class="<%= nav_text_class_for [root_path] %> flex flex-col items-center justify-center">
Expand Down
2 changes: 1 addition & 1 deletion app/views/layouts/_flash_message.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
<div data-controller="closable" class="bg-black/70 shadow-simple backdrop-blur-[25px] rounded-lg px-5 py-4 absolute transition-all bottom-0 max-w-screen-sm text-white z-20 sticky">
<%= message %>
</div>
</div>
</div>
2 changes: 1 addition & 1 deletion app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<% end %>
<% flash.each do |type, message| %>
<% if message.present? && message.is_a?(String) %>
<%= render partial: "layouts/flash_message", locals: { message: message }%>
<%= render partial: "layouts/flash_message", locals: { message: message } %>
<% end %>
<% end %>
<%= yield %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/main/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
<h1>Homepage</h1>
<%= link_to "Profile", profile_path %>
<%= link_to "Profile", profile_path(current_user.uuid) %>
1 change: 1 addition & 0 deletions app/views/profiles/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
</div>
</div>
</div>
<%= render inline: @profile.svg_qr_code(module_size: 4) %>
<div class="p-4 flex flex-col items-center gap-4 w-full">
<%= link_to 'Edit Profile', edit_profile_path, class: "text-red font-bold rounded-sm underline italic text-lg" %>
</div>
Expand Down
5 changes: 5 additions & 0 deletions config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
config.cache_store = :null_store
end

Rails.application.routes.default_url_options[:host] = "localhost:3000"

# Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local

Expand Down Expand Up @@ -82,4 +84,7 @@

# Raise error when a before_action's only/except options reference missing actions
config.action_controller.raise_on_missing_callback_actions = true

# Allow ngrok hosts
config.hosts << /[a-z0-9.\-]+\.ngrok\.io/
end
2 changes: 2 additions & 0 deletions config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false

Rails.application.routes.default_url_options[:host] = "localhost:3000"

# Store uploaded files on the local file system in a temporary directory.
config.active_storage.service = :test

Expand Down
6 changes: 3 additions & 3 deletions config/initializers/inflections.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
# end

# These inflection rules are supported but not enabled by default:
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.acronym "RESTful"
# end
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym "QR"
end
3 changes: 2 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
resource :registration, only: [:new, :create]
resource :session, only: [:new, :create, :destroy]
resource :password, only: [:edit, :update]
resource :profile, only: [:edit, :update, :show]
resource :password_reset, only: [:new, :create, :edit, :update] do
get :post_submit
end
resources :profiles, only: [:show], param: :uuid
resource :profile, only: [:edit, :update]
end
4 changes: 2 additions & 2 deletions config/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module.exports = {
theme: {
extend: {
boxShadow: {
'simple': '0px 0px 25px 0px rgba(0, 0, 0, 0.50)'
simple: '0px 0px 25px 0px rgba(0, 0, 0, 0.50)'
},
fontFamily: {
sans: ['Roboto', ...defaultTheme.fontFamily.sans]
Expand All @@ -31,7 +31,7 @@ module.exports = {
blue: '#0A4E6B',
'green-dark': '#62C554',
'green-light': '#D8F1D4'
},
}
}
},
plugins: [
Expand Down
6 changes: 6 additions & 0 deletions db/migrate/20240710213305_add_profile_uuid_to_profile.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class AddProfileUuidToProfile < ActiveRecord::Migration[7.1]
def change
add_column :profiles, :uuid, :string
add_index :profiles, :uuid, unique: true
end
end
2 changes: 2 additions & 0 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions spec/controllers/profiles_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe ProfilesController, type: :controller do
let(:user) { create(:user, :with_profile) }
let(:profile) { user.profile }
let(:other_profile) { create(:profile, :with_user) }

before do
sign_in(user)
end

describe "GET #show" do
context "when the profile is yourself" do
it "returns a success response" do
get :show, params: {uuid: profile.uuid}
expect(response).to have_http_status(:success)
end
end

context "when the profile is public" do
before do
other_profile.update!(is_public: true)
end

it "returns a success response" do
get :show, params: {uuid: other_profile.uuid}
expect(response).to have_http_status(:success)
end
end

context "when the profile is not public" do
it "raises a RecordNotFound error" do
expect { get :show, params: {uuid: other_profile.uuid} }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
end
11 changes: 11 additions & 0 deletions spec/decorators/profile_decorator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require "rails_helper"

RSpec.describe ProfileDecorator do
let(:profile) { create(:profile, :with_user).decorate }

describe "#svg_qr_code" do
it "generates a QR code SVG" do
expect(profile.svg_qr_code).to include("<svg")
end
end
end
9 changes: 8 additions & 1 deletion spec/factories/profiles.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
# name :string
# profileable_type :string not null
# twitter_url :string
# uuid :string
# created_at :datetime not null
# updated_at :datetime not null
# profileable_id :integer not null
#
# Indexes
#
# index_profiles_on_profileable (profileable_type,profileable_id)
# index_profiles_on_uuid (uuid) UNIQUE
#
FactoryBot.define do
factory :profile do
Expand All @@ -30,9 +32,14 @@
github_url { "https://github.com" }
linkedin_url { "https://linkedin.com" }
twitter_url { "https://twitter.com" }
uuid { SecureRandom.uuid }

trait :with_user do
association :profileable, factory: :user
profileable { create(:user) }
end

trait :public do
is_public { true }
end
end
end
4 changes: 3 additions & 1 deletion spec/factories/users.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
password { "password2024" }

trait :with_profile do
profile
after(:create) do |user|
create(:profile, profileable: user)
end
end
end
end
Loading

0 comments on commit e007d28

Please sign in to comment.