Skip to content

Commit

Permalink
Add feature of tagging users in photos (#415)
Browse files Browse the repository at this point in the history
* Add feature of tagging users in photos

* Fix lint

* Fix tests

* Increase coverage

* Fix lint
  • Loading branch information
wilco375 authored Oct 7, 2024
1 parent 3e13408 commit 3da6176
Show file tree
Hide file tree
Showing 23 changed files with 366 additions and 6 deletions.
2 changes: 2 additions & 0 deletions app/controllers/v1/photo_tags_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class V1::PhotoTagsController < V1::ApplicationController
end
6 changes: 6 additions & 0 deletions app/models/photo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ class Photo < ApplicationRecord
belongs_to :uploader, class_name: 'User'
has_many :comments, class_name: 'PhotoComment',
dependent: :destroy, counter_cache: :comments_count
has_many :tags, class_name: 'PhotoTag',
dependent: :destroy, counter_cache: :tags_count

mount_uploader :image, PhotoUploader
has_paper_trail skip: [:image]
Expand All @@ -19,6 +21,10 @@ class Photo < ApplicationRecord
joins(:comments).distinct
})

scope :with_tags, (lambda {
joins(:tags).distinct
})

scope :publicly_visible, (lambda {
joins(:photo_album).where(photo_albums: { publicly_visible: true })
})
Expand Down
19 changes: 19 additions & 0 deletions app/models/photo_tag.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class PhotoTag < ApplicationRecord
belongs_to :photo, touch: true
counter_culture :photo, column_name: :tags_count
belongs_to :author, class_name: 'User'
belongs_to :tagged_user, class_name: 'User'

validates :x, inclusion: { in: 0.0..100.0 }
validates :y, inclusion: { in: 0.0..100.0 }

validate :user_not_already_tagged

private

def user_not_already_tagged
if PhotoTag.exists?(photo_id: photo_id, tagged_user_id: tagged_user_id)
errors.add(:tagged_user, 'has already been tagged in this photo')
end
end
end
4 changes: 4 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength
has_many :article_comments, foreign_key: :author_id
has_many :board_room_presences, dependent: :delete_all
has_many :photo_comments, foreign_key: :author_id
has_many :created_photo_tags, class_name: 'PhotoTag', foreign_key: :author_id,
dependent: :delete_all
has_many :photo_tags, foreign_key: :tagged_user_id, dependent: :delete_all
has_many :photos, through: :photo_tags
has_many :mail_aliases, dependent: :delete_all
has_many :read_threads, class_name: 'Forum::ReadThread', dependent: :delete_all
has_many :mandates, class_name: 'Debit::Mandate', dependent: :delete_all
Expand Down
35 changes: 35 additions & 0 deletions app/policies/photo_tag_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class PhotoTagPolicy < ApplicationPolicy
def update?
user_is_owner? || user_is_tagged? || super
end

def destroy?
user_is_owner? || user_is_tagged? || super
end

def create_with_photo?(_photo)
true
end

def replace_photo?(_photo)
true
end

def create_with_tagged_user?(_user)
true
end

def replace_tagged_user?(_user)
true
end

private

def user_is_owner?
record.author == user
end

def user_is_tagged?
record.tagged_user == user
end
end
13 changes: 10 additions & 3 deletions app/resources/v1/photo_resource.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
class V1::PhotoResource < V1::ApplicationResource
attributes :image_url, :image_thumb_url, :image_medium_url, :amount_of_comments, :exif_make,
:exif_model, :exif_date_time_original, :exif_exposure_time, :exif_aperture_value,
:exif_iso_speed_ratings, :exif_copyright, :exif_lens_model, :exif_focal_length
attributes :image_url, :image_thumb_url, :image_medium_url, :amount_of_comments, :amount_of_tags,
:exif_make, :exif_model, :exif_date_time_original, :exif_exposure_time,
:exif_aperture_value, :exif_iso_speed_ratings, :exif_copyright, :exif_lens_model,
:exif_focal_length

def image_thumb_url
@model.image.thumb.url
Expand All @@ -15,9 +16,15 @@ def amount_of_comments
@model.comments.size
end

def amount_of_tags
@model.tags.size
end

filter :with_comments, apply: ->(records, _value, _options) { records.with_comments }
filter :with_tags, apply: ->(records, _value, _options) { records.with_tags }

has_one :photo_album, always_include_linkage_data: true
has_one :uploader, always_include_linkage_data: true
has_many :comments
has_many :tags
end
16 changes: 16 additions & 0 deletions app/resources/v1/photo_tag_resource.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class V1::PhotoTagResource < V1::ApplicationResource
attributes :x
attributes :y

has_one :photo, always_include_linkage_data: true
has_one :author, always_include_linkage_data: true
has_one :tagged_user, always_include_linkage_data: true

before_create do
@model.author_id = current_user.id
end

def self.creatable_fields(_context)
%i[x y photo tagged_user]
end
end
3 changes: 2 additions & 1 deletion app/resources/v1/user_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def avatar_thumb_url
has_many :mandates, always_include_linkage_data: true
has_many :group_mail_aliases
has_many :permissions
has_many :photos
has_many :user_permissions

filter :upcoming_birthdays, apply: lambda { |records, _value, options|
Expand Down Expand Up @@ -50,7 +51,7 @@ def fetchable_fields
avatar_url avatar_thumb_url created_at updated_at id]
# Relationships
allowed_keys += %i[groups active_groups memberships mail_aliases mandates
group_mail_aliases permissions user_permissions]
group_mail_aliases permissions photos user_permissions]
allowed_keys += %i[ical_secret_key webdav_secret_key] if me?
if update_or_me?
allowed_keys += %i[login_enabled otp_required activated_at emergency_contact
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
end
end
jsonapi_resources :photo_comments
jsonapi_resources :photo_tags
jsonapi_resources :photos, only: %i[index show destroy]
jsonapi_resources :polls
jsonapi_resources :room_adverts
Expand Down
20 changes: 20 additions & 0 deletions db/migrate/20240328205012_create_photo_tags.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class CreatePhotoTags < ActiveRecord::Migration[7.0]
def change
create_table :photo_tags do |t|
t.decimal :x, precision: 5, scale: 2
t.float :y, precision: 5, scale: 2
t.integer :author_id, null: false
t.integer :tagged_user_id, null: false
t.integer :photo_id, null: false
t.datetime :deleted_at

t.timestamps
end
add_column :photos, :tags_count, :integer, null: false, default: 0
end

Permission.create(name: 'photo_tag.create')
Permission.create(name: 'photo_tag.read')
Permission.create(name: 'photo_tag.update')
Permission.create(name: 'photo_tag.destroy')
end
14 changes: 13 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.0].define(version: 2023_07_27_114709) do
ActiveRecord::Schema[7.0].define(version: 2024_03_28_205012) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"

Expand Down Expand Up @@ -430,6 +430,17 @@
t.index ["photo_id"], name: "index_photo_comments_on_photo_id"
end

create_table "photo_tags", force: :cascade do |t|
t.decimal "x", precision: 5, scale: 2
t.float "y"
t.integer "author_id", null: false
t.integer "tagged_user_id", null: false
t.integer "photo_id", null: false
t.datetime "deleted_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end

create_table "photos", id: :serial, force: :cascade do |t|
t.string "image"
t.integer "photo_album_id"
Expand All @@ -448,6 +459,7 @@
t.string "exif_copyright"
t.string "exif_lens_model"
t.integer "exif_focal_length"
t.integer "tags_count", default: 0, null: false
t.index ["deleted_at"], name: "index_photos_on_deleted_at"
t.index ["photo_album_id"], name: "index_photos_on_photo_album_id"
t.index ["uploader_id"], name: "index_photos_on_uploader_id"
Expand Down
3 changes: 3 additions & 0 deletions db/seeds/permissions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def create_permissions(permission_map)
'photo_album' => %i[create read update destroy],
'photo' => %i[create read update destroy],
'photo_comment' => %i[create read update destroy],
'photo_tag' => %i[create read update destroy],
'quickpost_message' => %i[create read update destroy],
'room_advert' => %i[create read update destroy],
'stored_mail' => %i[read destroy],
Expand Down Expand Up @@ -72,6 +73,7 @@ def create_permissions(permission_map)
'photo_album' => %i[create read],
'photo' => %i[create read],
'photo_comment' => %i[create read],
'photo_tag' => %i[create read],
'quickpost_message' => %i[create read],
'room_advert' => %i[create read],
'forum/category' => %i[read],
Expand Down Expand Up @@ -105,6 +107,7 @@ def create_permissions(permission_map)
'photo_album' => %i[read],
'photo' => %i[read],
'photo_comment' => %i[create read],
'photo_tag' => %i[create read],
'quickpost_message' => %i[create read],
'room_advert' => %i[],
'forum/category' => %i[read],
Expand Down
10 changes: 10 additions & 0 deletions spec/factories/photo_tags.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FactoryBot.define do
factory :photo_tag do
x { rand(0.0..100.0) }
y { rand(0.0..100.0) }
association :author, factory: :user
association :tagged_user, factory: :user

photo
end
end
3 changes: 3 additions & 0 deletions spec/jobs/user_archive_job_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
create(:mail_alias, user: user)
create(:membership, user: user)
create(:permissions_users, user: user)
create(:photo_tag, author: user)
create(:photo_tag, tagged_user: user)
end

it { expect { job }.to change(BoardRoomPresence, :count).by(-1) }
Expand All @@ -55,6 +57,7 @@
it { expect { job }.to change(MailAlias, :count).by(-1) }
it { expect { job }.to change(Membership, :count).by(-1) }
it { expect { job }.to change(PermissionsUsers, :count).by(-1) }
it { expect { job }.to change(PhotoTag, :count).by(-2) }
it { expect { job }.to change { User.exists?(user.id) }.from(true).to(false) }
end
end
Expand Down
15 changes: 15 additions & 0 deletions spec/models/photo_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@
it { expect(described_class.with_comments.count).to be 2 }
end

describe '#with_tags' do
let(:photo_with_tags) { create(:photo) }
let(:photo_with_one_tag) { create(:photo) }

before do
create(:photo_tag, photo: photo_with_tags)
create(:photo_tag, photo: photo_with_tags)
create(:photo_tag, photo: photo_with_one_tag)
create(:photo)
end

it { expect(described_class.count).to be 3 }
it { expect(described_class.with_tags.count).to be 2 }
end

describe '#publicly_visible' do
let(:public_album) { create(:photo_album, publicly_visible: true) }
let(:private_album) { create(:photo_album, publicly_visible: false) }
Expand Down
68 changes: 68 additions & 0 deletions spec/models/photo_tag_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
require 'rails_helper'

RSpec.describe PhotoTag, type: :model do
subject(:photo_tag) { build(:photo_tag) }

describe '#valid?' do
it { expect(photo_tag).to be_valid }

context 'when without an x position' do
subject(:photo_tag) { build(:photo_tag, x: nil) }

it { expect(photo_tag).not_to be_valid }
end

context 'when without an y position' do
subject(:photo_tag) { build(:photo_tag, y: nil) }

it { expect(photo_tag).not_to be_valid }
end

context 'when with a too large x position' do
subject(:photo_tag) { build(:photo_tag, x: 101) }

it { expect(photo_tag).not_to be_valid }
end

context 'when with a too small x position' do
subject(:photo_tag) { build(:photo_tag, x: -1) }

it { expect(photo_tag).not_to be_valid }
end

context 'when with a too large y position' do
subject(:photo_tag) { build(:photo_tag, y: 101) }

it { expect(photo_tag).not_to be_valid }
end

context 'when with a too small y position' do
subject(:photo_tag) { build(:photo_tag, y: -1) }

it { expect(photo_tag).not_to be_valid }
end

context 'when with a valid x position' do
subject(:photo_tag) { build(:photo_tag, x: 50) }

it { expect(photo_tag).to be_valid }
end

context 'when with a valid y position' do
subject(:photo_tag) { build(:photo_tag, y: 50) }

it { expect(photo_tag).to be_valid }
end

context 'when with a user that is already tagged' do
let(:photo) { create(:photo) }
let(:tagged_user) { create(:user) }

before { create(:photo_tag, photo_id: photo.id, tagged_user_id: tagged_user.id) }

subject(:photo_tag) { build(:photo_tag, photo_id: photo.id, tagged_user_id: tagged_user.id) }

it { expect(photo_tag).not_to be_valid }
end
end
end
37 changes: 37 additions & 0 deletions spec/policies/photo_tag_policy_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require 'rails_helper'

RSpec.describe PhotoTagPolicy, type: :policy do
subject(:policy) { described_class }

let(:user) { build(:user) }

permissions :update? do
describe 'when photo tag is not owned or tagged' do
it { expect(policy).not_to permit(user, build(:photo_tag)) }
end

describe 'when photo tag is owned' do
it { expect(policy).to permit(user, build(:photo_tag, author: user)) }
end

describe 'when photo tag is tagged' do
it { expect(policy).to permit(user, build(:photo_tag, tagged_user: user)) }
end
end

describe '#create_with_photo?' do
it { expect(policy.new(nil, nil).create_with_photo?(nil)).to be true }
end

describe '#replace_photo?' do
it { expect(policy.new(nil, nil).replace_photo?(nil)).to be true }
end

describe '#create_with_tagged_user?' do
it { expect(policy.new(nil, nil).create_with_tagged_user?(nil)).to be true }
end

describe '#replace_tagged_user?' do
it { expect(policy.new(nil, nil).replace_tagged_user?(nil)).to be true }
end
end
Loading

0 comments on commit 3da6176

Please sign in to comment.