Files
manyfold/app/models/collection.rb
2026-02-25 15:05:13 +00:00

128 lines
4.6 KiB
Ruby

class Collection < ApplicationRecord
include Followable
include CaberObject
include Linkable
include Sluggable
include PublicIDable
include Commentable
include Indexable
include FaspClient::DataSharing::Lifecycle
broadcasts_refreshes
acts_as_federails_actor(
username_field: :public_id,
name_field: :name,
profile_url_method: :url_for,
# We use the Group actor type purely so Mastodon doesn't ignore the actor.
# Actual type is differentiated with f3di:concreteType == "Collection".
# Ideally this would be a Collection: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-collection
# Hopefully at some point this can change, if Mastodon starts allowing other actor types
# See https://github.com/mastodon/mastodon/issues/22322
actor_type: "Group"
)
has_many :models, dependent: :nullify
has_many :collections, dependent: :nullify
belongs_to :collection, optional: true
belongs_to :creator, optional: true
validates :name, uniqueness: {case_sensitive: false}, length: SAFE_NAME_LENGTH
validates :public_id, multimodel_uniqueness: {punctuation_sensitive: false, case_sensitive: false, check: FederailsCommon::FEDIVERSE_USERNAMES}
validates :collection_id, exclusion: {in: ->(it) { Array(it.id) }}
before_validation :publish_creator, if: :will_be_public?
validate :validate_publishable
after_create_commit :after_create
after_update_commit :after_update
fasp_share_lifecycle category: "account", uri_method: :fasp_uri, only_if: :public_and_indexable?
def fasp_uri
federails_actor&.federated_url
end
def name_with_domain
remote? ? name + " (#{federails_actor.server})" : name
end
# returns all collections at and below given ids
# used in Search::FilterService#parameter(:collection) to get models in sub-trees
scope :tree_down, ->(id) {
id ?
where("collections.id IN (With RECURSIVE search_tree(id) AS (
select id from collections where id IN (#{[*id].join(",")})
union all
select collections.id from search_tree join collections on collections.collection_id = search_tree.id where NOT collections.id IN (search_tree.id)
) select id from search_tree)") : where(collection_id: nil)
}
# returns root top-level collections for given ids
scope :tree_up, ->(id) {
id ?
where("collections.id IN (WITH RECURSIVE search_tree(id, path) AS (
SELECT id, id
FROM collections
WHERE collection_id is NULL
UNION ALL
SELECT collections.id, path FROM search_tree
JOIN collections ON collections.collection_id = search_tree.id
WHERE NOT collections.id IN (path)
) select id from search_tree where id IN (?))", id) : where(collection_id: nil)
}
# returns root top-level collections for given collection ids, limited by the top-level ids
# top: @filter[:collection]
# id: collections from models resulting from searches
scope :tree_both, ->(top, id) {
top ?
where("collections.id IN (WITH RECURSIVE search_tree(id, path) AS (
SELECT id, id
FROM collections
WHERE collection_id IN (#{[*top].join(",")})
UNION ALL
SELECT collections.id, path FROM search_tree
JOIN collections ON collections.collection_id = search_tree.id
WHERE NOT collections.id IN (path)
) select id from search_tree where id IN (?))", id) : tree_up(id)
}
# Basic query that returns list of all collections with their path as csv
# WITH RECURSIVE search_tree(id, path) AS (
# SELECT id, id
# FROM collections
# WHERE collection_id is NULL
# UNION ALL
# SELECT collections.id, path || ',' || collections.id
# FROM search_tree
# JOIN collections ON collections.collection_id = search_tree.id
# WHERE NOT collections.id IN (path)
# ) SELECT * FROM search_tree
def to_activitypub_object
ActivityPub::CollectionSerializer.new(self).serialize
end
def after_create
Activity::CollectionPublishedJob.set(wait: 5.seconds).perform_later(id) if public?
end
def after_update
Activity::CollectionPublishedJob.set(wait: 5.seconds).perform_later(id) if just_became_public?
end
def validate_publishable
# If the model will be public
if caber_relations.find { |it| it.subject.nil? }
# Check required fields
errors.add :creator, :private if creator && !creator.public?
errors.add :collection, :private if collection && !collection.public?
end
end
def publish_creator
creator&.update!(caber_relations_attributes: [{permission: "view", subject: nil}]) unless creator&.public?
end
end