Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature of tagging users in photos #415

Open
wants to merge 5 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading