diff --git a/api/funkwhale_api/audio/models.py b/api/funkwhale_api/audio/models.py index d2ebe5fe1..f97442d74 100644 --- a/api/funkwhale_api/audio/models.py +++ b/api/funkwhale_api/audio/models.py @@ -107,6 +107,4 @@ def generate_actor(username, **kwargs): @receiver(post_delete, sender=Channel) def delete_channel_related_objs(instance, **kwargs): instance.library.delete() - if instance.actor != instance.attributed_to: - instance.actor.delete() instance.artist.delete() diff --git a/api/funkwhale_api/audio/views.py b/api/funkwhale_api/audio/views.py index 01e29c659..bce39d54c 100644 --- a/api/funkwhale_api/audio/views.py +++ b/api/funkwhale_api/audio/views.py @@ -13,10 +13,12 @@ from django.utils import timezone from funkwhale_api.common import locales from funkwhale_api.common import permissions from funkwhale_api.common import preferences +from funkwhale_api.common import utils as common_utils from funkwhale_api.common.mixins import MultipleLookupDetailMixin from funkwhale_api.federation import actors from funkwhale_api.federation import models as federation_models from funkwhale_api.federation import routes +from funkwhale_api.federation import tasks as federation_tasks from funkwhale_api.federation import utils as federation_utils from funkwhale_api.music import models as music_models from funkwhale_api.music import views as music_views @@ -128,6 +130,8 @@ class ChannelViewSet( ) # prefetch stuff subscription = SubscriptionsViewSet.queryset.get(pk=subscription.pk) + if not object.actor.is_local: + routes.outbox.dispatch({"type": "Follow"}, context={"follow": subscription}) data = serializers.SubscriptionSerializer(subscription).data return response.Response(data, status=201) @@ -139,7 +143,15 @@ class ChannelViewSet( ) def unsubscribe(self, request, *args, **kwargs): object = self.get_object() - request.user.actor.emitted_follows.filter(target=object.actor).delete() + follow_qs = request.user.actor.emitted_follows.filter(target=object.actor) + follow = follow_qs.first() + if follow: + if not object.actor.is_local: + routes.outbox.dispatch( + {"type": "Undo", "object": {"type": "Follow"}}, + context={"follow": follow}, + ) + follow_qs.delete() return response.Response(status=204) @decorators.action( @@ -248,11 +260,10 @@ class ChannelViewSet( @transaction.atomic def perform_destroy(self, instance): - routes.outbox.dispatch( - {"type": "Delete", "object": {"type": instance.actor.type}}, - context={"actor": instance.actor}, - ) instance.__class__.objects.filter(pk=instance.pk).delete() + common_utils.on_commit( + federation_tasks.remove_actor.delay, actor_id=instance.actor.pk + ) class SubscriptionsViewSet( diff --git a/api/funkwhale_api/federation/api_serializers.py b/api/funkwhale_api/federation/api_serializers.py index 851f64a10..6ff1e1b2b 100644 --- a/api/funkwhale_api/federation/api_serializers.py +++ b/api/funkwhale_api/federation/api_serializers.py @@ -7,6 +7,7 @@ from django.utils import timezone from rest_framework import serializers +from funkwhale_api.audio import models as audio_models from funkwhale_api.common import fields as common_fields from funkwhale_api.common import serializers as common_serializers from funkwhale_api.music import models as music_models @@ -171,6 +172,7 @@ FETCH_OBJECT_CONFIG = { "library": {"queryset": music_models.Library.objects.all(), "id_attr": "uuid"}, "upload": {"queryset": music_models.Upload.objects.all(), "id_attr": "uuid"}, "account": {"queryset": models.Actor.objects.all(), "id_attr": "full_username"}, + "channel": {"queryset": audio_models.Channel.objects.all(), "id_attr": "uuid"}, } FETCH_OBJECT_FIELD = common_fields.GenericRelation(FETCH_OBJECT_CONFIG) diff --git a/api/funkwhale_api/federation/contexts.py b/api/funkwhale_api/federation/contexts.py index 3e61c03fb..a4eee1f71 100644 --- a/api/funkwhale_api/federation/contexts.py +++ b/api/funkwhale_api/federation/contexts.py @@ -1,3 +1,5 @@ +from . import schema_org + CONTEXTS = [ { "shortId": "LDP", @@ -218,6 +220,12 @@ CONTEXTS = [ } }, }, + { + "shortId": "SC", + "contextUrl": None, + "documentUrl": "http://schema.org", + "document": {"@context": schema_org.CONTEXT}, + }, { "shortId": "SEC", "contextUrl": None, @@ -280,6 +288,7 @@ CONTEXTS = [ "type": "@type", "as": "https://www.w3.org/ns/activitystreams#", "fw": "https://funkwhale.audio/ns#", + "schema": "http://schema.org#", "xsd": "http://www.w3.org/2001/XMLSchema#", "Album": "fw:Album", "Track": "fw:Track", @@ -298,6 +307,8 @@ CONTEXTS = [ "musicbrainzId": "fw:musicbrainzId", "license": {"@id": "fw:license", "@type": "@id"}, "copyright": "fw:copyright", + "category": "schema:category", + "language": "schema:inLanguage", } }, }, @@ -364,4 +375,5 @@ AS = NS(CONTEXTS_BY_ID["AS"]) LDP = NS(CONTEXTS_BY_ID["LDP"]) SEC = NS(CONTEXTS_BY_ID["SEC"]) FW = NS(CONTEXTS_BY_ID["FW"]) +SC = NS(CONTEXTS_BY_ID["SC"]) LITEPUB = NS(CONTEXTS_BY_ID["LITEPUB"]) diff --git a/api/funkwhale_api/federation/routes.py b/api/funkwhale_api/federation/routes.py index 70f312d1a..56eae7f12 100644 --- a/api/funkwhale_api/federation/routes.py +++ b/api/funkwhale_api/federation/routes.py @@ -1,5 +1,7 @@ import logging +from django.db.models import Q + from funkwhale_api.music import models as music_models from . import activity @@ -158,18 +160,26 @@ def outbox_create_audio(context): @inbox.register({"type": "Create", "object.type": "Audio"}) def inbox_create_audio(payload, context): - serializer = serializers.UploadSerializer( - data=payload["object"], - context={"activity": context.get("activity"), "actor": context["actor"]}, - ) - + is_channel = "library" not in payload["object"] + if is_channel: + channel = context["actor"].get_channel() + serializer = serializers.ChannelUploadSerializer( + data=payload["object"], context={"channel": channel}, + ) + else: + serializer = serializers.UploadSerializer( + data=payload["object"], + context={"activity": context.get("activity"), "actor": context["actor"]}, + ) if not serializer.is_valid(raise_exception=context.get("raise_exception", False)): logger.warn("Discarding invalid audio create") return upload = serializer.save() - - return {"object": upload, "target": upload.library} + if is_channel: + return {"object": upload, "target": channel} + else: + return {"object": upload, "target": upload.library} @inbox.register({"type": "Delete", "object.type": "Library"}) @@ -252,9 +262,10 @@ def inbox_delete_audio(payload, context): # we did not receive a list of Ids, so we can probably use the value directly upload_fids = [payload["object"]["id"]] - candidates = music_models.Upload.objects.filter( - library__actor=actor, fid__in=upload_fids + query = Q(fid__in=upload_fids) & ( + Q(library__actor=actor) | Q(track__artist__channel__actor=actor) ) + candidates = music_models.Upload.objects.filter(query) total = candidates.count() logger.info("Deleting %s uploads with ids %s", total, upload_fids) @@ -483,3 +494,44 @@ def outbox_flag(context): to=[{"type": "actor_inbox", "actor": report.target_owner}], ), } + + +@inbox.register({"type": "Delete", "object.type": "Album"}) +def inbox_delete_album(payload, context): + actor = context["actor"] + album_id = payload["object"].get("id") + if not album_id: + logger.debug("Discarding deletion of empty library") + return + + query = Q(fid=album_id) & (Q(attributed_to=actor) | Q(artist__channel__actor=actor)) + try: + album = music_models.Album.objects.get(query) + except music_models.Album.DoesNotExist: + logger.debug("Discarding deletion of unkwnown album %s", album_id) + return + + album.delete() + + +@outbox.register({"type": "Delete", "object.type": "Album"}) +def outbox_delete_album(context): + album = context["album"] + actor = ( + album.artist.channel.actor + if album.artist.get_channel() + else album.attributed_to + ) + actor = actor or actors.get_service_actor() + serializer = serializers.ActivitySerializer( + {"type": "Delete", "object": {"type": "Album", "id": album.fid}} + ) + + yield { + "type": "Delete", + "actor": actor, + "payload": with_recipients( + serializer.data, + to=[activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}], + ), + } diff --git a/api/funkwhale_api/federation/schema_org.py b/api/funkwhale_api/federation/schema_org.py new file mode 100644 index 000000000..82c1dc7e5 --- /dev/null +++ b/api/funkwhale_api/federation/schema_org.py @@ -0,0 +1,2579 @@ +CONTEXT = { + "type": "@type", + "id": "@id", + "HTML": {"@id": "rdf:HTML"}, + "@vocab": "http://schema.org/", + "xml": "http://www.w3.org/XML/1998/namespace", + "foaf": "http://xmlns.com/foaf/0.1/", + "eli": "http://data.europa.eu/eli/ontology#", + "snomed": "http://purl.bioontology.org/ontology/SNOMEDCT/", + "bibo": "http://purl.org/ontology/bibo/", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "skos": "http://www.w3.org/2004/02/skos/core#", + "void": "http://rdfs.org/ns/void#", + "dc": "http://purl.org/dc/elements/1.1/", + "dctype": "http://purl.org/dc/dcmitype/", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "dcat": "http://www.w3.org/ns/dcat#", + "rdfa": "http://www.w3.org/ns/rdfa#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "schema": "http://schema.org/", + "dct": "http://purl.org/dc/terms/", + "dcterms": "http://purl.org/dc/terms/", + "owl": "http://www.w3.org/2002/07/owl#", + "3DModel": {"@id": "schema:3DModel"}, + "AMRadioChannel": {"@id": "schema:AMRadioChannel"}, + "APIReference": {"@id": "schema:APIReference"}, + "Abdomen": {"@id": "schema:Abdomen"}, + "AboutPage": {"@id": "schema:AboutPage"}, + "AcceptAction": {"@id": "schema:AcceptAction"}, + "Accommodation": {"@id": "schema:Accommodation"}, + "AccountingService": {"@id": "schema:AccountingService"}, + "AchieveAction": {"@id": "schema:AchieveAction"}, + "Action": {"@id": "schema:Action"}, + "ActionAccessSpecification": {"@id": "schema:ActionAccessSpecification"}, + "ActionStatusType": {"@id": "schema:ActionStatusType"}, + "ActivateAction": {"@id": "schema:ActivateAction"}, + "ActiveActionStatus": {"@id": "schema:ActiveActionStatus"}, + "ActiveNotRecruiting": {"@id": "schema:ActiveNotRecruiting"}, + "AddAction": {"@id": "schema:AddAction"}, + "AdministrativeArea": {"@id": "schema:AdministrativeArea"}, + "AdultEntertainment": {"@id": "schema:AdultEntertainment"}, + "AdvertiserContentArticle": {"@id": "schema:AdvertiserContentArticle"}, + "AerobicActivity": {"@id": "schema:AerobicActivity"}, + "AggregateOffer": {"@id": "schema:AggregateOffer"}, + "AggregateRating": {"@id": "schema:AggregateRating"}, + "AgreeAction": {"@id": "schema:AgreeAction"}, + "Airline": {"@id": "schema:Airline"}, + "Airport": {"@id": "schema:Airport"}, + "AlbumRelease": {"@id": "schema:AlbumRelease"}, + "AlignmentObject": {"@id": "schema:AlignmentObject"}, + "AllWheelDriveConfiguration": {"@id": "schema:AllWheelDriveConfiguration"}, + "AllocateAction": {"@id": "schema:AllocateAction"}, + "AmusementPark": {"@id": "schema:AmusementPark"}, + "AnaerobicActivity": {"@id": "schema:AnaerobicActivity"}, + "AnalysisNewsArticle": {"@id": "schema:AnalysisNewsArticle"}, + "AnatomicalStructure": {"@id": "schema:AnatomicalStructure"}, + "AnatomicalSystem": {"@id": "schema:AnatomicalSystem"}, + "Anesthesia": {"@id": "schema:Anesthesia"}, + "AnimalShelter": {"@id": "schema:AnimalShelter"}, + "Answer": {"@id": "schema:Answer"}, + "Apartment": {"@id": "schema:Apartment"}, + "ApartmentComplex": {"@id": "schema:ApartmentComplex"}, + "Appearance": {"@id": "schema:Appearance"}, + "AppendAction": {"@id": "schema:AppendAction"}, + "ApplyAction": {"@id": "schema:ApplyAction"}, + "ApprovedIndication": {"@id": "schema:ApprovedIndication"}, + "Aquarium": {"@id": "schema:Aquarium"}, + "ArchiveComponent": {"@id": "schema:ArchiveComponent"}, + "ArchiveOrganization": {"@id": "schema:ArchiveOrganization"}, + "ArriveAction": {"@id": "schema:ArriveAction"}, + "ArtGallery": {"@id": "schema:ArtGallery"}, + "Artery": {"@id": "schema:Artery"}, + "Article": {"@id": "schema:Article"}, + "AskAction": {"@id": "schema:AskAction"}, + "AskPublicNewsArticle": {"@id": "schema:AskPublicNewsArticle"}, + "AssessAction": {"@id": "schema:AssessAction"}, + "AssignAction": {"@id": "schema:AssignAction"}, + "Atlas": {"@id": "schema:Atlas"}, + "Attorney": {"@id": "schema:Attorney"}, + "Audience": {"@id": "schema:Audience"}, + "AudioObject": {"@id": "schema:AudioObject"}, + "Audiobook": {"@id": "schema:Audiobook"}, + "AudiobookFormat": {"@id": "schema:AudiobookFormat"}, + "AuthenticContent": {"@id": "schema:AuthenticContent"}, + "AuthoritativeLegalValue": {"@id": "schema:AuthoritativeLegalValue"}, + "AuthorizeAction": {"@id": "schema:AuthorizeAction"}, + "AutoBodyShop": {"@id": "schema:AutoBodyShop"}, + "AutoDealer": {"@id": "schema:AutoDealer"}, + "AutoPartsStore": {"@id": "schema:AutoPartsStore"}, + "AutoRental": {"@id": "schema:AutoRental"}, + "AutoRepair": {"@id": "schema:AutoRepair"}, + "AutoWash": {"@id": "schema:AutoWash"}, + "AutomatedTeller": {"@id": "schema:AutomatedTeller"}, + "AutomotiveBusiness": {"@id": "schema:AutomotiveBusiness"}, + "Ayurvedic": {"@id": "schema:Ayurvedic"}, + "BackgroundNewsArticle": {"@id": "schema:BackgroundNewsArticle"}, + "Bacteria": {"@id": "schema:Bacteria"}, + "Bakery": {"@id": "schema:Bakery"}, + "Balance": {"@id": "schema:Balance"}, + "BankAccount": {"@id": "schema:BankAccount"}, + "BankOrCreditUnion": {"@id": "schema:BankOrCreditUnion"}, + "BarOrPub": {"@id": "schema:BarOrPub"}, + "Barcode": {"@id": "schema:Barcode"}, + "Beach": {"@id": "schema:Beach"}, + "BeautySalon": {"@id": "schema:BeautySalon"}, + "BedAndBreakfast": {"@id": "schema:BedAndBreakfast"}, + "BedDetails": {"@id": "schema:BedDetails"}, + "BedType": {"@id": "schema:BedType"}, + "BefriendAction": {"@id": "schema:BefriendAction"}, + "BenefitsHealthAspect": {"@id": "schema:BenefitsHealthAspect"}, + "BikeStore": {"@id": "schema:BikeStore"}, + "Blog": {"@id": "schema:Blog"}, + "BlogPosting": {"@id": "schema:BlogPosting"}, + "BloodTest": {"@id": "schema:BloodTest"}, + "BoardingPolicyType": {"@id": "schema:BoardingPolicyType"}, + "BodyOfWater": {"@id": "schema:BodyOfWater"}, + "Bone": {"@id": "schema:Bone"}, + "Book": {"@id": "schema:Book"}, + "BookFormatType": {"@id": "schema:BookFormatType"}, + "BookSeries": {"@id": "schema:BookSeries"}, + "BookStore": {"@id": "schema:BookStore"}, + "BookmarkAction": {"@id": "schema:BookmarkAction"}, + "Boolean": {"@id": "schema:Boolean"}, + "BorrowAction": {"@id": "schema:BorrowAction"}, + "BowlingAlley": {"@id": "schema:BowlingAlley"}, + "BrainStructure": {"@id": "schema:BrainStructure"}, + "Brand": {"@id": "schema:Brand"}, + "BreadcrumbList": {"@id": "schema:BreadcrumbList"}, + "Brewery": {"@id": "schema:Brewery"}, + "Bridge": {"@id": "schema:Bridge"}, + "BroadcastChannel": {"@id": "schema:BroadcastChannel"}, + "BroadcastEvent": {"@id": "schema:BroadcastEvent"}, + "BroadcastFrequencySpecification": { + "@id": "schema:BroadcastFrequencySpecification" + }, + "BroadcastRelease": {"@id": "schema:BroadcastRelease"}, + "BroadcastService": {"@id": "schema:BroadcastService"}, + "BrokerageAccount": {"@id": "schema:BrokerageAccount"}, + "BuddhistTemple": {"@id": "schema:BuddhistTemple"}, + "BusOrCoach": {"@id": "schema:BusOrCoach"}, + "BusReservation": {"@id": "schema:BusReservation"}, + "BusStation": {"@id": "schema:BusStation"}, + "BusStop": {"@id": "schema:BusStop"}, + "BusTrip": {"@id": "schema:BusTrip"}, + "BusinessAudience": {"@id": "schema:BusinessAudience"}, + "BusinessEntityType": {"@id": "schema:BusinessEntityType"}, + "BusinessEvent": {"@id": "schema:BusinessEvent"}, + "BusinessFunction": {"@id": "schema:BusinessFunction"}, + "BuyAction": {"@id": "schema:BuyAction"}, + "CDFormat": {"@id": "schema:CDFormat"}, + "CT": {"@id": "schema:CT"}, + "CableOrSatelliteService": {"@id": "schema:CableOrSatelliteService"}, + "CafeOrCoffeeShop": {"@id": "schema:CafeOrCoffeeShop"}, + "Campground": {"@id": "schema:Campground"}, + "CampingPitch": {"@id": "schema:CampingPitch"}, + "Canal": {"@id": "schema:Canal"}, + "CancelAction": {"@id": "schema:CancelAction"}, + "Car": {"@id": "schema:Car"}, + "CarUsageType": {"@id": "schema:CarUsageType"}, + "Cardiovascular": {"@id": "schema:Cardiovascular"}, + "CardiovascularExam": {"@id": "schema:CardiovascularExam"}, + "CaseSeries": {"@id": "schema:CaseSeries"}, + "Casino": {"@id": "schema:Casino"}, + "CassetteFormat": {"@id": "schema:CassetteFormat"}, + "CategoryCode": {"@id": "schema:CategoryCode"}, + "CategoryCodeSet": {"@id": "schema:CategoryCodeSet"}, + "CatholicChurch": {"@id": "schema:CatholicChurch"}, + "CausesHealthAspect": {"@id": "schema:CausesHealthAspect"}, + "Cemetery": {"@id": "schema:Cemetery"}, + "Chapter": {"@id": "schema:Chapter"}, + "CheckAction": {"@id": "schema:CheckAction"}, + "CheckInAction": {"@id": "schema:CheckInAction"}, + "CheckOutAction": {"@id": "schema:CheckOutAction"}, + "CheckoutPage": {"@id": "schema:CheckoutPage"}, + "ChildCare": {"@id": "schema:ChildCare"}, + "ChildrensEvent": {"@id": "schema:ChildrensEvent"}, + "Chiropractic": {"@id": "schema:Chiropractic"}, + "ChooseAction": {"@id": "schema:ChooseAction"}, + "Church": {"@id": "schema:Church"}, + "City": {"@id": "schema:City"}, + "CityHall": {"@id": "schema:CityHall"}, + "CivicStructure": {"@id": "schema:CivicStructure"}, + "Claim": {"@id": "schema:Claim"}, + "ClaimReview": {"@id": "schema:ClaimReview"}, + "Class": {"@id": "schema:Class"}, + "Clinician": {"@id": "schema:Clinician"}, + "Clip": {"@id": "schema:Clip"}, + "ClothingStore": {"@id": "schema:ClothingStore"}, + "CoOp": {"@id": "schema:CoOp"}, + "Code": {"@id": "schema:Code"}, + "CohortStudy": {"@id": "schema:CohortStudy"}, + "Collection": {"@id": "schema:Collection"}, + "CollectionPage": {"@id": "schema:CollectionPage"}, + "CollegeOrUniversity": {"@id": "schema:CollegeOrUniversity"}, + "ComedyClub": {"@id": "schema:ComedyClub"}, + "ComedyEvent": {"@id": "schema:ComedyEvent"}, + "ComicCoverArt": {"@id": "schema:ComicCoverArt"}, + "ComicIssue": {"@id": "schema:ComicIssue"}, + "ComicSeries": {"@id": "schema:ComicSeries"}, + "ComicStory": {"@id": "schema:ComicStory"}, + "Comment": {"@id": "schema:Comment"}, + "CommentAction": {"@id": "schema:CommentAction"}, + "CommentPermission": {"@id": "schema:CommentPermission"}, + "CommunicateAction": {"@id": "schema:CommunicateAction"}, + "CommunityHealth": {"@id": "schema:CommunityHealth"}, + "CompilationAlbum": {"@id": "schema:CompilationAlbum"}, + "CompleteDataFeed": {"@id": "schema:CompleteDataFeed"}, + "Completed": {"@id": "schema:Completed"}, + "CompletedActionStatus": {"@id": "schema:CompletedActionStatus"}, + "CompoundPriceSpecification": {"@id": "schema:CompoundPriceSpecification"}, + "ComputerLanguage": {"@id": "schema:ComputerLanguage"}, + "ComputerStore": {"@id": "schema:ComputerStore"}, + "ConfirmAction": {"@id": "schema:ConfirmAction"}, + "Consortium": {"@id": "schema:Consortium"}, + "ConsumeAction": {"@id": "schema:ConsumeAction"}, + "ContactPage": {"@id": "schema:ContactPage"}, + "ContactPoint": {"@id": "schema:ContactPoint"}, + "ContactPointOption": {"@id": "schema:ContactPointOption"}, + "ContagiousnessHealthAspect": {"@id": "schema:ContagiousnessHealthAspect"}, + "Continent": {"@id": "schema:Continent"}, + "ControlAction": {"@id": "schema:ControlAction"}, + "ConvenienceStore": {"@id": "schema:ConvenienceStore"}, + "Conversation": {"@id": "schema:Conversation"}, + "CookAction": {"@id": "schema:CookAction"}, + "Corporation": {"@id": "schema:Corporation"}, + "CorrectionComment": {"@id": "schema:CorrectionComment"}, + "Country": {"@id": "schema:Country"}, + "Course": {"@id": "schema:Course"}, + "CourseInstance": {"@id": "schema:CourseInstance"}, + "Courthouse": {"@id": "schema:Courthouse"}, + "CoverArt": {"@id": "schema:CoverArt"}, + "CovidTestingFacility": {"@id": "schema:CovidTestingFacility"}, + "CreateAction": {"@id": "schema:CreateAction"}, + "CreativeWork": {"@id": "schema:CreativeWork"}, + "CreativeWorkSeason": {"@id": "schema:CreativeWorkSeason"}, + "CreativeWorkSeries": {"@id": "schema:CreativeWorkSeries"}, + "CreditCard": {"@id": "schema:CreditCard"}, + "Crematorium": {"@id": "schema:Crematorium"}, + "CriticReview": {"@id": "schema:CriticReview"}, + "CrossSectional": {"@id": "schema:CrossSectional"}, + "CssSelectorType": {"@id": "schema:CssSelectorType"}, + "CurrencyConversionService": {"@id": "schema:CurrencyConversionService"}, + "DDxElement": {"@id": "schema:DDxElement"}, + "DJMixAlbum": {"@id": "schema:DJMixAlbum"}, + "DVDFormat": {"@id": "schema:DVDFormat"}, + "DamagedCondition": {"@id": "schema:DamagedCondition"}, + "DanceEvent": {"@id": "schema:DanceEvent"}, + "DanceGroup": {"@id": "schema:DanceGroup"}, + "DataCatalog": {"@id": "schema:DataCatalog"}, + "DataDownload": {"@id": "schema:DataDownload"}, + "DataFeed": {"@id": "schema:DataFeed"}, + "DataFeedItem": {"@id": "schema:DataFeedItem"}, + "DataType": {"@id": "schema:DataType"}, + "Dataset": {"@id": "schema:Dataset"}, + "Date": {"@id": "schema:Date"}, + "DateTime": {"@id": "schema:DateTime"}, + "DatedMoneySpecification": {"@id": "schema:DatedMoneySpecification"}, + "DayOfWeek": {"@id": "schema:DayOfWeek"}, + "DaySpa": {"@id": "schema:DaySpa"}, + "DeactivateAction": {"@id": "schema:DeactivateAction"}, + "DefenceEstablishment": {"@id": "schema:DefenceEstablishment"}, + "DefinedTerm": {"@id": "schema:DefinedTerm"}, + "DefinedTermSet": {"@id": "schema:DefinedTermSet"}, + "DefinitiveLegalValue": {"@id": "schema:DefinitiveLegalValue"}, + "DeleteAction": {"@id": "schema:DeleteAction"}, + "DeliveryChargeSpecification": {"@id": "schema:DeliveryChargeSpecification"}, + "DeliveryEvent": {"@id": "schema:DeliveryEvent"}, + "DeliveryMethod": {"@id": "schema:DeliveryMethod"}, + "Demand": {"@id": "schema:Demand"}, + "DemoAlbum": {"@id": "schema:DemoAlbum"}, + "Dentist": {"@id": "schema:Dentist"}, + "Dentistry": {"@id": "schema:Dentistry"}, + "DepartAction": {"@id": "schema:DepartAction"}, + "DepartmentStore": {"@id": "schema:DepartmentStore"}, + "DepositAccount": {"@id": "schema:DepositAccount"}, + "Dermatologic": {"@id": "schema:Dermatologic"}, + "Dermatology": {"@id": "schema:Dermatology"}, + "DiabeticDiet": {"@id": "schema:DiabeticDiet"}, + "Diagnostic": {"@id": "schema:Diagnostic"}, + "DiagnosticLab": {"@id": "schema:DiagnosticLab"}, + "DiagnosticProcedure": {"@id": "schema:DiagnosticProcedure"}, + "Diet": {"@id": "schema:Diet"}, + "DietNutrition": {"@id": "schema:DietNutrition"}, + "DietarySupplement": {"@id": "schema:DietarySupplement"}, + "DigitalAudioTapeFormat": {"@id": "schema:DigitalAudioTapeFormat"}, + "DigitalDocument": {"@id": "schema:DigitalDocument"}, + "DigitalDocumentPermission": {"@id": "schema:DigitalDocumentPermission"}, + "DigitalDocumentPermissionType": {"@id": "schema:DigitalDocumentPermissionType"}, + "DigitalFormat": {"@id": "schema:DigitalFormat"}, + "DisagreeAction": {"@id": "schema:DisagreeAction"}, + "Discontinued": {"@id": "schema:Discontinued"}, + "DiscoverAction": {"@id": "schema:DiscoverAction"}, + "DiscussionForumPosting": {"@id": "schema:DiscussionForumPosting"}, + "DislikeAction": {"@id": "schema:DislikeAction"}, + "Distance": {"@id": "schema:Distance"}, + "Distillery": {"@id": "schema:Distillery"}, + "DonateAction": {"@id": "schema:DonateAction"}, + "DoseSchedule": {"@id": "schema:DoseSchedule"}, + "DoubleBlindedTrial": {"@id": "schema:DoubleBlindedTrial"}, + "DownloadAction": {"@id": "schema:DownloadAction"}, + "DrawAction": {"@id": "schema:DrawAction"}, + "Drawing": {"@id": "schema:Drawing"}, + "DrinkAction": {"@id": "schema:DrinkAction"}, + "DriveWheelConfigurationValue": {"@id": "schema:DriveWheelConfigurationValue"}, + "DrivingSchoolVehicleUsage": {"@id": "schema:DrivingSchoolVehicleUsage"}, + "Drug": {"@id": "schema:Drug"}, + "DrugClass": {"@id": "schema:DrugClass"}, + "DrugCost": {"@id": "schema:DrugCost"}, + "DrugCostCategory": {"@id": "schema:DrugCostCategory"}, + "DrugLegalStatus": {"@id": "schema:DrugLegalStatus"}, + "DrugPregnancyCategory": {"@id": "schema:DrugPregnancyCategory"}, + "DrugPrescriptionStatus": {"@id": "schema:DrugPrescriptionStatus"}, + "DrugStrength": {"@id": "schema:DrugStrength"}, + "DryCleaningOrLaundry": {"@id": "schema:DryCleaningOrLaundry"}, + "Duration": {"@id": "schema:Duration"}, + "EBook": {"@id": "schema:EBook"}, + "EPRelease": {"@id": "schema:EPRelease"}, + "Ear": {"@id": "schema:Ear"}, + "EatAction": {"@id": "schema:EatAction"}, + "EducationEvent": {"@id": "schema:EducationEvent"}, + "EducationalAudience": {"@id": "schema:EducationalAudience"}, + "EducationalOccupationalCredential": { + "@id": "schema:EducationalOccupationalCredential" + }, + "EducationalOccupationalProgram": {"@id": "schema:EducationalOccupationalProgram"}, + "EducationalOrganization": {"@id": "schema:EducationalOrganization"}, + "Electrician": {"@id": "schema:Electrician"}, + "ElectronicsStore": {"@id": "schema:ElectronicsStore"}, + "ElementarySchool": {"@id": "schema:ElementarySchool"}, + "EmailMessage": {"@id": "schema:EmailMessage"}, + "Embassy": {"@id": "schema:Embassy"}, + "Emergency": {"@id": "schema:Emergency"}, + "EmergencyService": {"@id": "schema:EmergencyService"}, + "EmployeeRole": {"@id": "schema:EmployeeRole"}, + "EmployerAggregateRating": {"@id": "schema:EmployerAggregateRating"}, + "EmployerReview": {"@id": "schema:EmployerReview"}, + "EmploymentAgency": {"@id": "schema:EmploymentAgency"}, + "Endocrine": {"@id": "schema:Endocrine"}, + "EndorseAction": {"@id": "schema:EndorseAction"}, + "EndorsementRating": {"@id": "schema:EndorsementRating"}, + "Energy": {"@id": "schema:Energy"}, + "EngineSpecification": {"@id": "schema:EngineSpecification"}, + "EnrollingByInvitation": {"@id": "schema:EnrollingByInvitation"}, + "EntertainmentBusiness": {"@id": "schema:EntertainmentBusiness"}, + "EntryPoint": {"@id": "schema:EntryPoint"}, + "Enumeration": {"@id": "schema:Enumeration"}, + "Episode": {"@id": "schema:Episode"}, + "Event": {"@id": "schema:Event"}, + "EventAttendanceModeEnumeration": {"@id": "schema:EventAttendanceModeEnumeration"}, + "EventCancelled": {"@id": "schema:EventCancelled"}, + "EventMovedOnline": {"@id": "schema:EventMovedOnline"}, + "EventPostponed": {"@id": "schema:EventPostponed"}, + "EventRescheduled": {"@id": "schema:EventRescheduled"}, + "EventReservation": {"@id": "schema:EventReservation"}, + "EventScheduled": {"@id": "schema:EventScheduled"}, + "EventSeries": {"@id": "schema:EventSeries"}, + "EventStatusType": {"@id": "schema:EventStatusType"}, + "EventVenue": {"@id": "schema:EventVenue"}, + "EvidenceLevelA": {"@id": "schema:EvidenceLevelA"}, + "EvidenceLevelB": {"@id": "schema:EvidenceLevelB"}, + "EvidenceLevelC": {"@id": "schema:EvidenceLevelC"}, + "ExchangeRateSpecification": {"@id": "schema:ExchangeRateSpecification"}, + "ExchangeRefund": {"@id": "schema:ExchangeRefund"}, + "ExerciseAction": {"@id": "schema:ExerciseAction"}, + "ExerciseGym": {"@id": "schema:ExerciseGym"}, + "ExercisePlan": {"@id": "schema:ExercisePlan"}, + "ExhibitionEvent": {"@id": "schema:ExhibitionEvent"}, + "Eye": {"@id": "schema:Eye"}, + "FAQPage": {"@id": "schema:FAQPage"}, + "FDAcategoryA": {"@id": "schema:FDAcategoryA"}, + "FDAcategoryB": {"@id": "schema:FDAcategoryB"}, + "FDAcategoryC": {"@id": "schema:FDAcategoryC"}, + "FDAcategoryD": {"@id": "schema:FDAcategoryD"}, + "FDAcategoryX": {"@id": "schema:FDAcategoryX"}, + "FDAnotEvaluated": {"@id": "schema:FDAnotEvaluated"}, + "FMRadioChannel": {"@id": "schema:FMRadioChannel"}, + "FailedActionStatus": {"@id": "schema:FailedActionStatus"}, + "False": {"@id": "schema:False"}, + "FastFoodRestaurant": {"@id": "schema:FastFoodRestaurant"}, + "Female": {"@id": "schema:Female"}, + "Festival": {"@id": "schema:Festival"}, + "FilmAction": {"@id": "schema:FilmAction"}, + "FinancialProduct": {"@id": "schema:FinancialProduct"}, + "FinancialService": {"@id": "schema:FinancialService"}, + "FindAction": {"@id": "schema:FindAction"}, + "FireStation": {"@id": "schema:FireStation"}, + "Flexibility": {"@id": "schema:Flexibility"}, + "Flight": {"@id": "schema:Flight"}, + "FlightReservation": {"@id": "schema:FlightReservation"}, + "Float": {"@id": "schema:Float"}, + "FloorPlan": {"@id": "schema:FloorPlan"}, + "Florist": {"@id": "schema:Florist"}, + "FollowAction": {"@id": "schema:FollowAction"}, + "FoodEstablishment": {"@id": "schema:FoodEstablishment"}, + "FoodEstablishmentReservation": {"@id": "schema:FoodEstablishmentReservation"}, + "FoodEvent": {"@id": "schema:FoodEvent"}, + "FoodService": {"@id": "schema:FoodService"}, + "FourWheelDriveConfiguration": {"@id": "schema:FourWheelDriveConfiguration"}, + "Friday": {"@id": "schema:Friday"}, + "FrontWheelDriveConfiguration": {"@id": "schema:FrontWheelDriveConfiguration"}, + "FullRefund": {"@id": "schema:FullRefund"}, + "FundingAgency": {"@id": "schema:FundingAgency"}, + "FundingScheme": {"@id": "schema:FundingScheme"}, + "Fungus": {"@id": "schema:Fungus"}, + "FurnitureStore": {"@id": "schema:FurnitureStore"}, + "Game": {"@id": "schema:Game"}, + "GamePlayMode": {"@id": "schema:GamePlayMode"}, + "GameServer": {"@id": "schema:GameServer"}, + "GameServerStatus": {"@id": "schema:GameServerStatus"}, + "GardenStore": {"@id": "schema:GardenStore"}, + "GasStation": {"@id": "schema:GasStation"}, + "Gastroenterologic": {"@id": "schema:Gastroenterologic"}, + "GatedResidenceCommunity": {"@id": "schema:GatedResidenceCommunity"}, + "GenderType": {"@id": "schema:GenderType"}, + "GeneralContractor": {"@id": "schema:GeneralContractor"}, + "Genetic": {"@id": "schema:Genetic"}, + "Genitourinary": {"@id": "schema:Genitourinary"}, + "GeoCircle": {"@id": "schema:GeoCircle"}, + "GeoCoordinates": {"@id": "schema:GeoCoordinates"}, + "GeoShape": {"@id": "schema:GeoShape"}, + "GeospatialGeometry": {"@id": "schema:GeospatialGeometry"}, + "Geriatric": {"@id": "schema:Geriatric"}, + "GiveAction": {"@id": "schema:GiveAction"}, + "GlutenFreeDiet": {"@id": "schema:GlutenFreeDiet"}, + "GolfCourse": {"@id": "schema:GolfCourse"}, + "GovernmentBuilding": {"@id": "schema:GovernmentBuilding"}, + "GovernmentOffice": {"@id": "schema:GovernmentOffice"}, + "GovernmentOrganization": {"@id": "schema:GovernmentOrganization"}, + "GovernmentPermit": {"@id": "schema:GovernmentPermit"}, + "GovernmentService": {"@id": "schema:GovernmentService"}, + "Grant": {"@id": "schema:Grant"}, + "GraphicNovel": {"@id": "schema:GraphicNovel"}, + "GroceryStore": {"@id": "schema:GroceryStore"}, + "GroupBoardingPolicy": {"@id": "schema:GroupBoardingPolicy"}, + "Guide": {"@id": "schema:Guide"}, + "Gynecologic": {"@id": "schema:Gynecologic"}, + "HVACBusiness": {"@id": "schema:HVACBusiness"}, + "HairSalon": {"@id": "schema:HairSalon"}, + "HalalDiet": {"@id": "schema:HalalDiet"}, + "Hardcover": {"@id": "schema:Hardcover"}, + "HardwareStore": {"@id": "schema:HardwareStore"}, + "Head": {"@id": "schema:Head"}, + "HealthAndBeautyBusiness": {"@id": "schema:HealthAndBeautyBusiness"}, + "HealthAspectEnumeration": {"@id": "schema:HealthAspectEnumeration"}, + "HealthClub": {"@id": "schema:HealthClub"}, + "HealthInsurancePlan": {"@id": "schema:HealthInsurancePlan"}, + "HealthPlanCostSharingSpecification": { + "@id": "schema:HealthPlanCostSharingSpecification" + }, + "HealthPlanFormulary": {"@id": "schema:HealthPlanFormulary"}, + "HealthPlanNetwork": {"@id": "schema:HealthPlanNetwork"}, + "HealthTopicContent": {"@id": "schema:HealthTopicContent"}, + "HearingImpairedSupported": {"@id": "schema:HearingImpairedSupported"}, + "Hematologic": {"@id": "schema:Hematologic"}, + "HighSchool": {"@id": "schema:HighSchool"}, + "HinduDiet": {"@id": "schema:HinduDiet"}, + "HinduTemple": {"@id": "schema:HinduTemple"}, + "HobbyShop": {"@id": "schema:HobbyShop"}, + "HomeAndConstructionBusiness": {"@id": "schema:HomeAndConstructionBusiness"}, + "HomeGoodsStore": {"@id": "schema:HomeGoodsStore"}, + "Homeopathic": {"@id": "schema:Homeopathic"}, + "Hospital": {"@id": "schema:Hospital"}, + "Hostel": {"@id": "schema:Hostel"}, + "Hotel": {"@id": "schema:Hotel"}, + "HotelRoom": {"@id": "schema:HotelRoom"}, + "House": {"@id": "schema:House"}, + "HousePainter": {"@id": "schema:HousePainter"}, + "HowOrWhereHealthAspect": {"@id": "schema:HowOrWhereHealthAspect"}, + "HowTo": {"@id": "schema:HowTo"}, + "HowToDirection": {"@id": "schema:HowToDirection"}, + "HowToItem": {"@id": "schema:HowToItem"}, + "HowToSection": {"@id": "schema:HowToSection"}, + "HowToStep": {"@id": "schema:HowToStep"}, + "HowToSupply": {"@id": "schema:HowToSupply"}, + "HowToTip": {"@id": "schema:HowToTip"}, + "HowToTool": {"@id": "schema:HowToTool"}, + "IceCreamShop": {"@id": "schema:IceCreamShop"}, + "IgnoreAction": {"@id": "schema:IgnoreAction"}, + "ImageGallery": {"@id": "schema:ImageGallery"}, + "ImageObject": {"@id": "schema:ImageObject"}, + "ImagingTest": {"@id": "schema:ImagingTest"}, + "InForce": {"@id": "schema:InForce"}, + "InStock": {"@id": "schema:InStock"}, + "InStoreOnly": {"@id": "schema:InStoreOnly"}, + "IndividualProduct": {"@id": "schema:IndividualProduct"}, + "Infectious": {"@id": "schema:Infectious"}, + "InfectiousAgentClass": {"@id": "schema:InfectiousAgentClass"}, + "InfectiousDisease": {"@id": "schema:InfectiousDisease"}, + "InformAction": {"@id": "schema:InformAction"}, + "InsertAction": {"@id": "schema:InsertAction"}, + "InstallAction": {"@id": "schema:InstallAction"}, + "InsuranceAgency": {"@id": "schema:InsuranceAgency"}, + "Intangible": {"@id": "schema:Intangible"}, + "Integer": {"@id": "schema:Integer"}, + "InteractAction": {"@id": "schema:InteractAction"}, + "InteractionCounter": {"@id": "schema:InteractionCounter"}, + "InternationalTrial": {"@id": "schema:InternationalTrial"}, + "InternetCafe": {"@id": "schema:InternetCafe"}, + "InvestmentFund": {"@id": "schema:InvestmentFund"}, + "InvestmentOrDeposit": {"@id": "schema:InvestmentOrDeposit"}, + "InviteAction": {"@id": "schema:InviteAction"}, + "Invoice": {"@id": "schema:Invoice"}, + "ItemAvailability": {"@id": "schema:ItemAvailability"}, + "ItemList": {"@id": "schema:ItemList"}, + "ItemListOrderAscending": {"@id": "schema:ItemListOrderAscending"}, + "ItemListOrderDescending": {"@id": "schema:ItemListOrderDescending"}, + "ItemListOrderType": {"@id": "schema:ItemListOrderType"}, + "ItemListUnordered": {"@id": "schema:ItemListUnordered"}, + "ItemPage": {"@id": "schema:ItemPage"}, + "JewelryStore": {"@id": "schema:JewelryStore"}, + "JobPosting": {"@id": "schema:JobPosting"}, + "JoinAction": {"@id": "schema:JoinAction"}, + "Joint": {"@id": "schema:Joint"}, + "KosherDiet": {"@id": "schema:KosherDiet"}, + "LaboratoryScience": {"@id": "schema:LaboratoryScience"}, + "LakeBodyOfWater": {"@id": "schema:LakeBodyOfWater"}, + "Landform": {"@id": "schema:Landform"}, + "LandmarksOrHistoricalBuildings": {"@id": "schema:LandmarksOrHistoricalBuildings"}, + "Language": {"@id": "schema:Language"}, + "LaserDiscFormat": {"@id": "schema:LaserDiscFormat"}, + "LeaveAction": {"@id": "schema:LeaveAction"}, + "LeftHandDriving": {"@id": "schema:LeftHandDriving"}, + "LegalForceStatus": {"@id": "schema:LegalForceStatus"}, + "LegalService": {"@id": "schema:LegalService"}, + "LegalValueLevel": {"@id": "schema:LegalValueLevel"}, + "Legislation": {"@id": "schema:Legislation"}, + "LegislationObject": {"@id": "schema:LegislationObject"}, + "LegislativeBuilding": {"@id": "schema:LegislativeBuilding"}, + "LeisureTimeActivity": {"@id": "schema:LeisureTimeActivity"}, + "LendAction": {"@id": "schema:LendAction"}, + "Library": {"@id": "schema:Library"}, + "LibrarySystem": {"@id": "schema:LibrarySystem"}, + "LifestyleModification": {"@id": "schema:LifestyleModification"}, + "Ligament": {"@id": "schema:Ligament"}, + "LikeAction": {"@id": "schema:LikeAction"}, + "LimitedAvailability": {"@id": "schema:LimitedAvailability"}, + "LinkRole": {"@id": "schema:LinkRole"}, + "LiquorStore": {"@id": "schema:LiquorStore"}, + "ListItem": {"@id": "schema:ListItem"}, + "ListenAction": {"@id": "schema:ListenAction"}, + "LiteraryEvent": {"@id": "schema:LiteraryEvent"}, + "LiveAlbum": {"@id": "schema:LiveAlbum"}, + "LiveBlogPosting": {"@id": "schema:LiveBlogPosting"}, + "LivingWithHealthAspect": {"@id": "schema:LivingWithHealthAspect"}, + "LoanOrCredit": {"@id": "schema:LoanOrCredit"}, + "LocalBusiness": {"@id": "schema:LocalBusiness"}, + "LocationFeatureSpecification": {"@id": "schema:LocationFeatureSpecification"}, + "LockerDelivery": {"@id": "schema:LockerDelivery"}, + "Locksmith": {"@id": "schema:Locksmith"}, + "LodgingBusiness": {"@id": "schema:LodgingBusiness"}, + "LodgingReservation": {"@id": "schema:LodgingReservation"}, + "Longitudinal": {"@id": "schema:Longitudinal"}, + "LoseAction": {"@id": "schema:LoseAction"}, + "LowCalorieDiet": {"@id": "schema:LowCalorieDiet"}, + "LowFatDiet": {"@id": "schema:LowFatDiet"}, + "LowLactoseDiet": {"@id": "schema:LowLactoseDiet"}, + "LowSaltDiet": {"@id": "schema:LowSaltDiet"}, + "Lung": {"@id": "schema:Lung"}, + "LymphaticVessel": {"@id": "schema:LymphaticVessel"}, + "MRI": {"@id": "schema:MRI"}, + "Male": {"@id": "schema:Male"}, + "Manuscript": {"@id": "schema:Manuscript"}, + "Map": {"@id": "schema:Map"}, + "MapCategoryType": {"@id": "schema:MapCategoryType"}, + "MarryAction": {"@id": "schema:MarryAction"}, + "Mass": {"@id": "schema:Mass"}, + "MaximumDoseSchedule": {"@id": "schema:MaximumDoseSchedule"}, + "MayTreatHealthAspect": {"@id": "schema:MayTreatHealthAspect"}, + "MediaGallery": {"@id": "schema:MediaGallery"}, + "MediaManipulationRatingEnumeration": { + "@id": "schema:MediaManipulationRatingEnumeration" + }, + "MediaObject": {"@id": "schema:MediaObject"}, + "MediaReview": {"@id": "schema:MediaReview"}, + "MediaSubscription": {"@id": "schema:MediaSubscription"}, + "MedicalAudience": {"@id": "schema:MedicalAudience"}, + "MedicalBusiness": {"@id": "schema:MedicalBusiness"}, + "MedicalCause": {"@id": "schema:MedicalCause"}, + "MedicalClinic": {"@id": "schema:MedicalClinic"}, + "MedicalCode": {"@id": "schema:MedicalCode"}, + "MedicalCondition": {"@id": "schema:MedicalCondition"}, + "MedicalConditionStage": {"@id": "schema:MedicalConditionStage"}, + "MedicalContraindication": {"@id": "schema:MedicalContraindication"}, + "MedicalDevice": {"@id": "schema:MedicalDevice"}, + "MedicalDevicePurpose": {"@id": "schema:MedicalDevicePurpose"}, + "MedicalEntity": {"@id": "schema:MedicalEntity"}, + "MedicalEnumeration": {"@id": "schema:MedicalEnumeration"}, + "MedicalEvidenceLevel": {"@id": "schema:MedicalEvidenceLevel"}, + "MedicalGuideline": {"@id": "schema:MedicalGuideline"}, + "MedicalGuidelineContraindication": { + "@id": "schema:MedicalGuidelineContraindication" + }, + "MedicalGuidelineRecommendation": {"@id": "schema:MedicalGuidelineRecommendation"}, + "MedicalImagingTechnique": {"@id": "schema:MedicalImagingTechnique"}, + "MedicalIndication": {"@id": "schema:MedicalIndication"}, + "MedicalIntangible": {"@id": "schema:MedicalIntangible"}, + "MedicalObservationalStudy": {"@id": "schema:MedicalObservationalStudy"}, + "MedicalObservationalStudyDesign": { + "@id": "schema:MedicalObservationalStudyDesign" + }, + "MedicalOrganization": {"@id": "schema:MedicalOrganization"}, + "MedicalProcedure": {"@id": "schema:MedicalProcedure"}, + "MedicalProcedureType": {"@id": "schema:MedicalProcedureType"}, + "MedicalResearcher": {"@id": "schema:MedicalResearcher"}, + "MedicalRiskCalculator": {"@id": "schema:MedicalRiskCalculator"}, + "MedicalRiskEstimator": {"@id": "schema:MedicalRiskEstimator"}, + "MedicalRiskFactor": {"@id": "schema:MedicalRiskFactor"}, + "MedicalRiskScore": {"@id": "schema:MedicalRiskScore"}, + "MedicalScholarlyArticle": {"@id": "schema:MedicalScholarlyArticle"}, + "MedicalSign": {"@id": "schema:MedicalSign"}, + "MedicalSignOrSymptom": {"@id": "schema:MedicalSignOrSymptom"}, + "MedicalSpecialty": {"@id": "schema:MedicalSpecialty"}, + "MedicalStudy": {"@id": "schema:MedicalStudy"}, + "MedicalStudyStatus": {"@id": "schema:MedicalStudyStatus"}, + "MedicalSymptom": {"@id": "schema:MedicalSymptom"}, + "MedicalTest": {"@id": "schema:MedicalTest"}, + "MedicalTestPanel": {"@id": "schema:MedicalTestPanel"}, + "MedicalTherapy": {"@id": "schema:MedicalTherapy"}, + "MedicalTrial": {"@id": "schema:MedicalTrial"}, + "MedicalTrialDesign": {"@id": "schema:MedicalTrialDesign"}, + "MedicalWebPage": {"@id": "schema:MedicalWebPage"}, + "MedicineSystem": {"@id": "schema:MedicineSystem"}, + "MeetingRoom": {"@id": "schema:MeetingRoom"}, + "MensClothingStore": {"@id": "schema:MensClothingStore"}, + "Menu": {"@id": "schema:Menu"}, + "MenuItem": {"@id": "schema:MenuItem"}, + "MenuSection": {"@id": "schema:MenuSection"}, + "MerchantReturnEnumeration": {"@id": "schema:MerchantReturnEnumeration"}, + "MerchantReturnFiniteReturnWindow": { + "@id": "schema:MerchantReturnFiniteReturnWindow" + }, + "MerchantReturnNotPermitted": {"@id": "schema:MerchantReturnNotPermitted"}, + "MerchantReturnPolicy": {"@id": "schema:MerchantReturnPolicy"}, + "MerchantReturnUnlimitedWindow": {"@id": "schema:MerchantReturnUnlimitedWindow"}, + "MerchantReturnUnspecified": {"@id": "schema:MerchantReturnUnspecified"}, + "Message": {"@id": "schema:Message"}, + "MiddleSchool": {"@id": "schema:MiddleSchool"}, + "Midwifery": {"@id": "schema:Midwifery"}, + "MisconceptionsHealthAspect": {"@id": "schema:MisconceptionsHealthAspect"}, + "MissingContext": {"@id": "schema:MissingContext"}, + "MixedEventAttendanceMode": {"@id": "schema:MixedEventAttendanceMode"}, + "MixtapeAlbum": {"@id": "schema:MixtapeAlbum"}, + "MobileApplication": {"@id": "schema:MobileApplication"}, + "MobilePhoneStore": {"@id": "schema:MobilePhoneStore"}, + "Monday": {"@id": "schema:Monday"}, + "MonetaryAmount": {"@id": "schema:MonetaryAmount"}, + "MonetaryAmountDistribution": {"@id": "schema:MonetaryAmountDistribution"}, + "MonetaryGrant": {"@id": "schema:MonetaryGrant"}, + "MoneyTransfer": {"@id": "schema:MoneyTransfer"}, + "MortgageLoan": {"@id": "schema:MortgageLoan"}, + "Mosque": {"@id": "schema:Mosque"}, + "Motel": {"@id": "schema:Motel"}, + "Motorcycle": {"@id": "schema:Motorcycle"}, + "MotorcycleDealer": {"@id": "schema:MotorcycleDealer"}, + "MotorcycleRepair": {"@id": "schema:MotorcycleRepair"}, + "MotorizedBicycle": {"@id": "schema:MotorizedBicycle"}, + "Mountain": {"@id": "schema:Mountain"}, + "MoveAction": {"@id": "schema:MoveAction"}, + "Movie": {"@id": "schema:Movie"}, + "MovieClip": {"@id": "schema:MovieClip"}, + "MovieRentalStore": {"@id": "schema:MovieRentalStore"}, + "MovieSeries": {"@id": "schema:MovieSeries"}, + "MovieTheater": {"@id": "schema:MovieTheater"}, + "MovingCompany": {"@id": "schema:MovingCompany"}, + "MultiCenterTrial": {"@id": "schema:MultiCenterTrial"}, + "MultiPlayer": {"@id": "schema:MultiPlayer"}, + "MulticellularParasite": {"@id": "schema:MulticellularParasite"}, + "Muscle": {"@id": "schema:Muscle"}, + "Musculoskeletal": {"@id": "schema:Musculoskeletal"}, + "MusculoskeletalExam": {"@id": "schema:MusculoskeletalExam"}, + "Museum": {"@id": "schema:Museum"}, + "MusicAlbum": {"@id": "schema:MusicAlbum"}, + "MusicAlbumProductionType": {"@id": "schema:MusicAlbumProductionType"}, + "MusicAlbumReleaseType": {"@id": "schema:MusicAlbumReleaseType"}, + "MusicComposition": {"@id": "schema:MusicComposition"}, + "MusicEvent": {"@id": "schema:MusicEvent"}, + "MusicGroup": {"@id": "schema:MusicGroup"}, + "MusicPlaylist": {"@id": "schema:MusicPlaylist"}, + "MusicRecording": {"@id": "schema:MusicRecording"}, + "MusicRelease": {"@id": "schema:MusicRelease"}, + "MusicReleaseFormatType": {"@id": "schema:MusicReleaseFormatType"}, + "MusicStore": {"@id": "schema:MusicStore"}, + "MusicVenue": {"@id": "schema:MusicVenue"}, + "MusicVideoObject": {"@id": "schema:MusicVideoObject"}, + "NGO": {"@id": "schema:NGO"}, + "NailSalon": {"@id": "schema:NailSalon"}, + "Neck": {"@id": "schema:Neck"}, + "Nerve": {"@id": "schema:Nerve"}, + "Neuro": {"@id": "schema:Neuro"}, + "Neurologic": {"@id": "schema:Neurologic"}, + "NewCondition": {"@id": "schema:NewCondition"}, + "NewsArticle": {"@id": "schema:NewsArticle"}, + "NewsMediaOrganization": {"@id": "schema:NewsMediaOrganization"}, + "Newspaper": {"@id": "schema:Newspaper"}, + "NightClub": {"@id": "schema:NightClub"}, + "NoninvasiveProcedure": {"@id": "schema:NoninvasiveProcedure"}, + "Nose": {"@id": "schema:Nose"}, + "NotInForce": {"@id": "schema:NotInForce"}, + "NotYetRecruiting": {"@id": "schema:NotYetRecruiting"}, + "Notary": {"@id": "schema:Notary"}, + "NoteDigitalDocument": {"@id": "schema:NoteDigitalDocument"}, + "Number": {"@id": "schema:Number"}, + "Nursing": {"@id": "schema:Nursing"}, + "NutritionInformation": {"@id": "schema:NutritionInformation"}, + "OTC": {"@id": "schema:OTC"}, + "Observation": {"@id": "schema:Observation"}, + "Observational": {"@id": "schema:Observational"}, + "Obstetric": {"@id": "schema:Obstetric"}, + "Occupation": {"@id": "schema:Occupation"}, + "OccupationalActivity": {"@id": "schema:OccupationalActivity"}, + "OccupationalTherapy": {"@id": "schema:OccupationalTherapy"}, + "OceanBodyOfWater": {"@id": "schema:OceanBodyOfWater"}, + "Offer": {"@id": "schema:Offer"}, + "OfferCatalog": {"@id": "schema:OfferCatalog"}, + "OfferForLease": {"@id": "schema:OfferForLease"}, + "OfferForPurchase": {"@id": "schema:OfferForPurchase"}, + "OfferItemCondition": {"@id": "schema:OfferItemCondition"}, + "OfficeEquipmentStore": {"@id": "schema:OfficeEquipmentStore"}, + "OfficialLegalValue": {"@id": "schema:OfficialLegalValue"}, + "OfflineEventAttendanceMode": {"@id": "schema:OfflineEventAttendanceMode"}, + "OfflinePermanently": {"@id": "schema:OfflinePermanently"}, + "OfflineTemporarily": {"@id": "schema:OfflineTemporarily"}, + "OnDemandEvent": {"@id": "schema:OnDemandEvent"}, + "OnSitePickup": {"@id": "schema:OnSitePickup"}, + "Oncologic": {"@id": "schema:Oncologic"}, + "Online": {"@id": "schema:Online"}, + "OnlineEventAttendanceMode": {"@id": "schema:OnlineEventAttendanceMode"}, + "OnlineFull": {"@id": "schema:OnlineFull"}, + "OnlineOnly": {"@id": "schema:OnlineOnly"}, + "OpenTrial": {"@id": "schema:OpenTrial"}, + "OpeningHoursSpecification": {"@id": "schema:OpeningHoursSpecification"}, + "OpinionNewsArticle": {"@id": "schema:OpinionNewsArticle"}, + "Optician": {"@id": "schema:Optician"}, + "Optometric": {"@id": "schema:Optometric"}, + "Order": {"@id": "schema:Order"}, + "OrderAction": {"@id": "schema:OrderAction"}, + "OrderCancelled": {"@id": "schema:OrderCancelled"}, + "OrderDelivered": {"@id": "schema:OrderDelivered"}, + "OrderInTransit": {"@id": "schema:OrderInTransit"}, + "OrderItem": {"@id": "schema:OrderItem"}, + "OrderPaymentDue": {"@id": "schema:OrderPaymentDue"}, + "OrderPickupAvailable": {"@id": "schema:OrderPickupAvailable"}, + "OrderProblem": {"@id": "schema:OrderProblem"}, + "OrderProcessing": {"@id": "schema:OrderProcessing"}, + "OrderReturned": {"@id": "schema:OrderReturned"}, + "OrderStatus": {"@id": "schema:OrderStatus"}, + "Organization": {"@id": "schema:Organization"}, + "OrganizationRole": {"@id": "schema:OrganizationRole"}, + "OrganizeAction": {"@id": "schema:OrganizeAction"}, + "OriginalShippingFees": {"@id": "schema:OriginalShippingFees"}, + "Osteopathic": {"@id": "schema:Osteopathic"}, + "Otolaryngologic": {"@id": "schema:Otolaryngologic"}, + "OutOfStock": {"@id": "schema:OutOfStock"}, + "OutletStore": {"@id": "schema:OutletStore"}, + "OverviewHealthAspect": {"@id": "schema:OverviewHealthAspect"}, + "OwnershipInfo": {"@id": "schema:OwnershipInfo"}, + "PET": {"@id": "schema:PET"}, + "PaintAction": {"@id": "schema:PaintAction"}, + "Painting": {"@id": "schema:Painting"}, + "PalliativeProcedure": {"@id": "schema:PalliativeProcedure"}, + "Paperback": {"@id": "schema:Paperback"}, + "ParcelDelivery": {"@id": "schema:ParcelDelivery"}, + "ParcelService": {"@id": "schema:ParcelService"}, + "ParentAudience": {"@id": "schema:ParentAudience"}, + "Park": {"@id": "schema:Park"}, + "ParkingFacility": {"@id": "schema:ParkingFacility"}, + "ParkingMap": {"@id": "schema:ParkingMap"}, + "PartiallyInForce": {"@id": "schema:PartiallyInForce"}, + "Pathology": {"@id": "schema:Pathology"}, + "PathologyTest": {"@id": "schema:PathologyTest"}, + "Patient": {"@id": "schema:Patient"}, + "PatientExperienceHealthAspect": {"@id": "schema:PatientExperienceHealthAspect"}, + "PawnShop": {"@id": "schema:PawnShop"}, + "PayAction": {"@id": "schema:PayAction"}, + "PaymentAutomaticallyApplied": {"@id": "schema:PaymentAutomaticallyApplied"}, + "PaymentCard": {"@id": "schema:PaymentCard"}, + "PaymentChargeSpecification": {"@id": "schema:PaymentChargeSpecification"}, + "PaymentComplete": {"@id": "schema:PaymentComplete"}, + "PaymentDeclined": {"@id": "schema:PaymentDeclined"}, + "PaymentDue": {"@id": "schema:PaymentDue"}, + "PaymentMethod": {"@id": "schema:PaymentMethod"}, + "PaymentPastDue": {"@id": "schema:PaymentPastDue"}, + "PaymentService": {"@id": "schema:PaymentService"}, + "PaymentStatusType": {"@id": "schema:PaymentStatusType"}, + "Pediatric": {"@id": "schema:Pediatric"}, + "PeopleAudience": {"@id": "schema:PeopleAudience"}, + "PercutaneousProcedure": {"@id": "schema:PercutaneousProcedure"}, + "PerformAction": {"@id": "schema:PerformAction"}, + "PerformanceRole": {"@id": "schema:PerformanceRole"}, + "PerformingArtsTheater": {"@id": "schema:PerformingArtsTheater"}, + "PerformingGroup": {"@id": "schema:PerformingGroup"}, + "Periodical": {"@id": "schema:Periodical"}, + "Permit": {"@id": "schema:Permit"}, + "Person": {"@id": "schema:Person"}, + "PetStore": {"@id": "schema:PetStore"}, + "Pharmacy": {"@id": "schema:Pharmacy"}, + "PharmacySpecialty": {"@id": "schema:PharmacySpecialty"}, + "Photograph": {"@id": "schema:Photograph"}, + "PhotographAction": {"@id": "schema:PhotographAction"}, + "PhysicalActivity": {"@id": "schema:PhysicalActivity"}, + "PhysicalActivityCategory": {"@id": "schema:PhysicalActivityCategory"}, + "PhysicalExam": {"@id": "schema:PhysicalExam"}, + "PhysicalTherapy": {"@id": "schema:PhysicalTherapy"}, + "Physician": {"@id": "schema:Physician"}, + "Physiotherapy": {"@id": "schema:Physiotherapy"}, + "Place": {"@id": "schema:Place"}, + "PlaceOfWorship": {"@id": "schema:PlaceOfWorship"}, + "PlaceboControlledTrial": {"@id": "schema:PlaceboControlledTrial"}, + "PlanAction": {"@id": "schema:PlanAction"}, + "PlasticSurgery": {"@id": "schema:PlasticSurgery"}, + "Play": {"@id": "schema:Play"}, + "PlayAction": {"@id": "schema:PlayAction"}, + "Playground": {"@id": "schema:Playground"}, + "Plumber": {"@id": "schema:Plumber"}, + "PodcastEpisode": {"@id": "schema:PodcastEpisode"}, + "PodcastSeason": {"@id": "schema:PodcastSeason"}, + "PodcastSeries": {"@id": "schema:PodcastSeries"}, + "Podiatric": {"@id": "schema:Podiatric"}, + "PoliceStation": {"@id": "schema:PoliceStation"}, + "Pond": {"@id": "schema:Pond"}, + "PostOffice": {"@id": "schema:PostOffice"}, + "PostalAddress": {"@id": "schema:PostalAddress"}, + "Poster": {"@id": "schema:Poster"}, + "PotentialActionStatus": {"@id": "schema:PotentialActionStatus"}, + "PreOrder": {"@id": "schema:PreOrder"}, + "PreOrderAction": {"@id": "schema:PreOrderAction"}, + "PreSale": {"@id": "schema:PreSale"}, + "PrependAction": {"@id": "schema:PrependAction"}, + "Preschool": {"@id": "schema:Preschool"}, + "PrescriptionOnly": {"@id": "schema:PrescriptionOnly"}, + "PresentationDigitalDocument": {"@id": "schema:PresentationDigitalDocument"}, + "PreventionHealthAspect": {"@id": "schema:PreventionHealthAspect"}, + "PreventionIndication": {"@id": "schema:PreventionIndication"}, + "PriceSpecification": {"@id": "schema:PriceSpecification"}, + "PrimaryCare": {"@id": "schema:PrimaryCare"}, + "Prion": {"@id": "schema:Prion"}, + "Product": {"@id": "schema:Product"}, + "ProductModel": {"@id": "schema:ProductModel"}, + "ProductReturnEnumeration": {"@id": "schema:ProductReturnEnumeration"}, + "ProductReturnFiniteReturnWindow": { + "@id": "schema:ProductReturnFiniteReturnWindow" + }, + "ProductReturnNotPermitted": {"@id": "schema:ProductReturnNotPermitted"}, + "ProductReturnPolicy": {"@id": "schema:ProductReturnPolicy"}, + "ProductReturnUnlimitedWindow": {"@id": "schema:ProductReturnUnlimitedWindow"}, + "ProductReturnUnspecified": {"@id": "schema:ProductReturnUnspecified"}, + "ProfessionalService": {"@id": "schema:ProfessionalService"}, + "ProfilePage": {"@id": "schema:ProfilePage"}, + "PrognosisHealthAspect": {"@id": "schema:PrognosisHealthAspect"}, + "ProgramMembership": {"@id": "schema:ProgramMembership"}, + "Project": {"@id": "schema:Project"}, + "PronounceableText": {"@id": "schema:PronounceableText"}, + "Property": {"@id": "schema:Property"}, + "PropertyValue": {"@id": "schema:PropertyValue"}, + "PropertyValueSpecification": {"@id": "schema:PropertyValueSpecification"}, + "Protozoa": {"@id": "schema:Protozoa"}, + "Psychiatric": {"@id": "schema:Psychiatric"}, + "PsychologicalTreatment": {"@id": "schema:PsychologicalTreatment"}, + "PublicHealth": {"@id": "schema:PublicHealth"}, + "PublicHolidays": {"@id": "schema:PublicHolidays"}, + "PublicSwimmingPool": {"@id": "schema:PublicSwimmingPool"}, + "PublicToilet": {"@id": "schema:PublicToilet"}, + "PublicationEvent": {"@id": "schema:PublicationEvent"}, + "PublicationIssue": {"@id": "schema:PublicationIssue"}, + "PublicationVolume": {"@id": "schema:PublicationVolume"}, + "Pulmonary": {"@id": "schema:Pulmonary"}, + "QAPage": {"@id": "schema:QAPage"}, + "QualitativeValue": {"@id": "schema:QualitativeValue"}, + "QuantitativeValue": {"@id": "schema:QuantitativeValue"}, + "QuantitativeValueDistribution": {"@id": "schema:QuantitativeValueDistribution"}, + "Quantity": {"@id": "schema:Quantity"}, + "Question": {"@id": "schema:Question"}, + "Quotation": {"@id": "schema:Quotation"}, + "QuoteAction": {"@id": "schema:QuoteAction"}, + "RVPark": {"@id": "schema:RVPark"}, + "RadiationTherapy": {"@id": "schema:RadiationTherapy"}, + "RadioBroadcastService": {"@id": "schema:RadioBroadcastService"}, + "RadioChannel": {"@id": "schema:RadioChannel"}, + "RadioClip": {"@id": "schema:RadioClip"}, + "RadioEpisode": {"@id": "schema:RadioEpisode"}, + "RadioSeason": {"@id": "schema:RadioSeason"}, + "RadioSeries": {"@id": "schema:RadioSeries"}, + "RadioStation": {"@id": "schema:RadioStation"}, + "Radiography": {"@id": "schema:Radiography"}, + "RandomizedTrial": {"@id": "schema:RandomizedTrial"}, + "Rating": {"@id": "schema:Rating"}, + "ReactAction": {"@id": "schema:ReactAction"}, + "ReadAction": {"@id": "schema:ReadAction"}, + "ReadPermission": {"@id": "schema:ReadPermission"}, + "RealEstateAgent": {"@id": "schema:RealEstateAgent"}, + "RealEstateListing": {"@id": "schema:RealEstateListing"}, + "RearWheelDriveConfiguration": {"@id": "schema:RearWheelDriveConfiguration"}, + "ReceiveAction": {"@id": "schema:ReceiveAction"}, + "Recipe": {"@id": "schema:Recipe"}, + "Recommendation": {"@id": "schema:Recommendation"}, + "RecommendedDoseSchedule": {"@id": "schema:RecommendedDoseSchedule"}, + "Recruiting": {"@id": "schema:Recruiting"}, + "RecyclingCenter": {"@id": "schema:RecyclingCenter"}, + "RefundTypeEnumeration": {"@id": "schema:RefundTypeEnumeration"}, + "RefurbishedCondition": {"@id": "schema:RefurbishedCondition"}, + "RegisterAction": {"@id": "schema:RegisterAction"}, + "Registry": {"@id": "schema:Registry"}, + "ReimbursementCap": {"@id": "schema:ReimbursementCap"}, + "RejectAction": {"@id": "schema:RejectAction"}, + "RelatedTopicsHealthAspect": {"@id": "schema:RelatedTopicsHealthAspect"}, + "RemixAlbum": {"@id": "schema:RemixAlbum"}, + "Renal": {"@id": "schema:Renal"}, + "RentAction": {"@id": "schema:RentAction"}, + "RentalCarReservation": {"@id": "schema:RentalCarReservation"}, + "RentalVehicleUsage": {"@id": "schema:RentalVehicleUsage"}, + "RepaymentSpecification": {"@id": "schema:RepaymentSpecification"}, + "ReplaceAction": {"@id": "schema:ReplaceAction"}, + "ReplyAction": {"@id": "schema:ReplyAction"}, + "Report": {"@id": "schema:Report"}, + "ReportageNewsArticle": {"@id": "schema:ReportageNewsArticle"}, + "ReportedDoseSchedule": {"@id": "schema:ReportedDoseSchedule"}, + "ResearchProject": {"@id": "schema:ResearchProject"}, + "Researcher": {"@id": "schema:Researcher"}, + "Reservation": {"@id": "schema:Reservation"}, + "ReservationCancelled": {"@id": "schema:ReservationCancelled"}, + "ReservationConfirmed": {"@id": "schema:ReservationConfirmed"}, + "ReservationHold": {"@id": "schema:ReservationHold"}, + "ReservationPackage": {"@id": "schema:ReservationPackage"}, + "ReservationPending": {"@id": "schema:ReservationPending"}, + "ReservationStatusType": {"@id": "schema:ReservationStatusType"}, + "ReserveAction": {"@id": "schema:ReserveAction"}, + "Reservoir": {"@id": "schema:Reservoir"}, + "Residence": {"@id": "schema:Residence"}, + "Resort": {"@id": "schema:Resort"}, + "RespiratoryTherapy": {"@id": "schema:RespiratoryTherapy"}, + "Restaurant": {"@id": "schema:Restaurant"}, + "RestockingFees": {"@id": "schema:RestockingFees"}, + "RestrictedDiet": {"@id": "schema:RestrictedDiet"}, + "ResultsAvailable": {"@id": "schema:ResultsAvailable"}, + "ResultsNotAvailable": {"@id": "schema:ResultsNotAvailable"}, + "ResumeAction": {"@id": "schema:ResumeAction"}, + "Retail": {"@id": "schema:Retail"}, + "ReturnAction": {"@id": "schema:ReturnAction"}, + "ReturnFeesEnumeration": {"@id": "schema:ReturnFeesEnumeration"}, + "ReturnShippingFees": {"@id": "schema:ReturnShippingFees"}, + "Review": {"@id": "schema:Review"}, + "ReviewAction": {"@id": "schema:ReviewAction"}, + "ReviewNewsArticle": {"@id": "schema:ReviewNewsArticle"}, + "Rheumatologic": {"@id": "schema:Rheumatologic"}, + "RightHandDriving": {"@id": "schema:RightHandDriving"}, + "RisksOrComplicationsHealthAspect": { + "@id": "schema:RisksOrComplicationsHealthAspect" + }, + "RiverBodyOfWater": {"@id": "schema:RiverBodyOfWater"}, + "Role": {"@id": "schema:Role"}, + "RoofingContractor": {"@id": "schema:RoofingContractor"}, + "Room": {"@id": "schema:Room"}, + "RsvpAction": {"@id": "schema:RsvpAction"}, + "RsvpResponseMaybe": {"@id": "schema:RsvpResponseMaybe"}, + "RsvpResponseNo": {"@id": "schema:RsvpResponseNo"}, + "RsvpResponseType": {"@id": "schema:RsvpResponseType"}, + "RsvpResponseYes": {"@id": "schema:RsvpResponseYes"}, + "SaleEvent": {"@id": "schema:SaleEvent"}, + "SatiricalArticle": {"@id": "schema:SatiricalArticle"}, + "Saturday": {"@id": "schema:Saturday"}, + "Schedule": {"@id": "schema:Schedule"}, + "ScheduleAction": {"@id": "schema:ScheduleAction"}, + "ScholarlyArticle": {"@id": "schema:ScholarlyArticle"}, + "School": {"@id": "schema:School"}, + "SchoolDistrict": {"@id": "schema:SchoolDistrict"}, + "ScreeningEvent": {"@id": "schema:ScreeningEvent"}, + "ScreeningHealthAspect": {"@id": "schema:ScreeningHealthAspect"}, + "Sculpture": {"@id": "schema:Sculpture"}, + "SeaBodyOfWater": {"@id": "schema:SeaBodyOfWater"}, + "SearchAction": {"@id": "schema:SearchAction"}, + "SearchResultsPage": {"@id": "schema:SearchResultsPage"}, + "Season": {"@id": "schema:Season"}, + "Seat": {"@id": "schema:Seat"}, + "SeatingMap": {"@id": "schema:SeatingMap"}, + "SeeDoctorHealthAspect": {"@id": "schema:SeeDoctorHealthAspect"}, + "SelfCareHealthAspect": {"@id": "schema:SelfCareHealthAspect"}, + "SelfStorage": {"@id": "schema:SelfStorage"}, + "SellAction": {"@id": "schema:SellAction"}, + "SendAction": {"@id": "schema:SendAction"}, + "Series": {"@id": "schema:Series"}, + "Service": {"@id": "schema:Service"}, + "ServiceChannel": {"@id": "schema:ServiceChannel"}, + "ShareAction": {"@id": "schema:ShareAction"}, + "SheetMusic": {"@id": "schema:SheetMusic"}, + "ShoeStore": {"@id": "schema:ShoeStore"}, + "ShoppingCenter": {"@id": "schema:ShoppingCenter"}, + "ShortStory": {"@id": "schema:ShortStory"}, + "SideEffectsHealthAspect": {"@id": "schema:SideEffectsHealthAspect"}, + "SingleBlindedTrial": {"@id": "schema:SingleBlindedTrial"}, + "SingleCenterTrial": {"@id": "schema:SingleCenterTrial"}, + "SingleFamilyResidence": {"@id": "schema:SingleFamilyResidence"}, + "SinglePlayer": {"@id": "schema:SinglePlayer"}, + "SingleRelease": {"@id": "schema:SingleRelease"}, + "SiteNavigationElement": {"@id": "schema:SiteNavigationElement"}, + "SkiResort": {"@id": "schema:SkiResort"}, + "Skin": {"@id": "schema:Skin"}, + "SocialEvent": {"@id": "schema:SocialEvent"}, + "SocialMediaPosting": {"@id": "schema:SocialMediaPosting"}, + "SoftwareApplication": {"@id": "schema:SoftwareApplication"}, + "SoftwareSourceCode": {"@id": "schema:SoftwareSourceCode"}, + "SoldOut": {"@id": "schema:SoldOut"}, + "SomeProducts": {"@id": "schema:SomeProducts"}, + "SoundtrackAlbum": {"@id": "schema:SoundtrackAlbum"}, + "SpeakableSpecification": {"@id": "schema:SpeakableSpecification"}, + "SpecialAnnouncement": {"@id": "schema:SpecialAnnouncement"}, + "Specialty": {"@id": "schema:Specialty"}, + "SpeechPathology": {"@id": "schema:SpeechPathology"}, + "SpokenWordAlbum": {"@id": "schema:SpokenWordAlbum"}, + "SportingGoodsStore": {"@id": "schema:SportingGoodsStore"}, + "SportsActivityLocation": {"@id": "schema:SportsActivityLocation"}, + "SportsClub": {"@id": "schema:SportsClub"}, + "SportsEvent": {"@id": "schema:SportsEvent"}, + "SportsOrganization": {"@id": "schema:SportsOrganization"}, + "SportsTeam": {"@id": "schema:SportsTeam"}, + "SpreadsheetDigitalDocument": {"@id": "schema:SpreadsheetDigitalDocument"}, + "StadiumOrArena": {"@id": "schema:StadiumOrArena"}, + "StagesHealthAspect": {"@id": "schema:StagesHealthAspect"}, + "State": {"@id": "schema:State"}, + "StatisticalPopulation": {"@id": "schema:StatisticalPopulation"}, + "SteeringPositionValue": {"@id": "schema:SteeringPositionValue"}, + "Store": {"@id": "schema:Store"}, + "StoreCreditRefund": {"@id": "schema:StoreCreditRefund"}, + "StrengthTraining": {"@id": "schema:StrengthTraining"}, + "StructuredValue": {"@id": "schema:StructuredValue"}, + "StudioAlbum": {"@id": "schema:StudioAlbum"}, + "StupidType": {"@id": "schema:StupidType"}, + "SubscribeAction": {"@id": "schema:SubscribeAction"}, + "Substance": {"@id": "schema:Substance"}, + "SubwayStation": {"@id": "schema:SubwayStation"}, + "Suite": {"@id": "schema:Suite"}, + "Sunday": {"@id": "schema:Sunday"}, + "SuperficialAnatomy": {"@id": "schema:SuperficialAnatomy"}, + "Surgical": {"@id": "schema:Surgical"}, + "SurgicalProcedure": {"@id": "schema:SurgicalProcedure"}, + "SuspendAction": {"@id": "schema:SuspendAction"}, + "Suspended": {"@id": "schema:Suspended"}, + "SymptomsHealthAspect": {"@id": "schema:SymptomsHealthAspect"}, + "Synagogue": {"@id": "schema:Synagogue"}, + "TVClip": {"@id": "schema:TVClip"}, + "TVEpisode": {"@id": "schema:TVEpisode"}, + "TVSeason": {"@id": "schema:TVSeason"}, + "TVSeries": {"@id": "schema:TVSeries"}, + "Table": {"@id": "schema:Table"}, + "TakeAction": {"@id": "schema:TakeAction"}, + "TattooParlor": {"@id": "schema:TattooParlor"}, + "Taxi": {"@id": "schema:Taxi"}, + "TaxiReservation": {"@id": "schema:TaxiReservation"}, + "TaxiService": {"@id": "schema:TaxiService"}, + "TaxiStand": {"@id": "schema:TaxiStand"}, + "TaxiVehicleUsage": {"@id": "schema:TaxiVehicleUsage"}, + "TechArticle": {"@id": "schema:TechArticle"}, + "TelevisionChannel": {"@id": "schema:TelevisionChannel"}, + "TelevisionStation": {"@id": "schema:TelevisionStation"}, + "TennisComplex": {"@id": "schema:TennisComplex"}, + "Terminated": {"@id": "schema:Terminated"}, + "Text": {"@id": "schema:Text"}, + "TextDigitalDocument": {"@id": "schema:TextDigitalDocument"}, + "TheaterEvent": {"@id": "schema:TheaterEvent"}, + "TheaterGroup": {"@id": "schema:TheaterGroup"}, + "Therapeutic": {"@id": "schema:Therapeutic"}, + "TherapeuticProcedure": {"@id": "schema:TherapeuticProcedure"}, + "Thesis": {"@id": "schema:Thesis"}, + "Thing": {"@id": "schema:Thing"}, + "Throat": {"@id": "schema:Throat"}, + "Thursday": {"@id": "schema:Thursday"}, + "Ticket": {"@id": "schema:Ticket"}, + "TieAction": {"@id": "schema:TieAction"}, + "Time": {"@id": "schema:Time"}, + "TipAction": {"@id": "schema:TipAction"}, + "TireShop": {"@id": "schema:TireShop"}, + "TollFree": {"@id": "schema:TollFree"}, + "TouristAttraction": {"@id": "schema:TouristAttraction"}, + "TouristDestination": {"@id": "schema:TouristDestination"}, + "TouristInformationCenter": {"@id": "schema:TouristInformationCenter"}, + "TouristTrip": {"@id": "schema:TouristTrip"}, + "Toxicologic": {"@id": "schema:Toxicologic"}, + "ToyStore": {"@id": "schema:ToyStore"}, + "TrackAction": {"@id": "schema:TrackAction"}, + "TradeAction": {"@id": "schema:TradeAction"}, + "TraditionalChinese": {"@id": "schema:TraditionalChinese"}, + "TrainReservation": {"@id": "schema:TrainReservation"}, + "TrainStation": {"@id": "schema:TrainStation"}, + "TrainTrip": {"@id": "schema:TrainTrip"}, + "TransferAction": {"@id": "schema:TransferAction"}, + "TransitMap": {"@id": "schema:TransitMap"}, + "TravelAction": {"@id": "schema:TravelAction"}, + "TravelAgency": {"@id": "schema:TravelAgency"}, + "TreatmentIndication": {"@id": "schema:TreatmentIndication"}, + "TreatmentsHealthAspect": {"@id": "schema:TreatmentsHealthAspect"}, + "Trip": {"@id": "schema:Trip"}, + "TripleBlindedTrial": {"@id": "schema:TripleBlindedTrial"}, + "True": {"@id": "schema:True"}, + "Tuesday": {"@id": "schema:Tuesday"}, + "TypeAndQuantityNode": {"@id": "schema:TypeAndQuantityNode"}, + "TypesHealthAspect": {"@id": "schema:TypesHealthAspect"}, + "URL": {"@id": "schema:URL"}, + "Ultrasound": {"@id": "schema:Ultrasound"}, + "UnRegisterAction": {"@id": "schema:UnRegisterAction"}, + "UnitPriceSpecification": {"@id": "schema:UnitPriceSpecification"}, + "UnofficialLegalValue": {"@id": "schema:UnofficialLegalValue"}, + "UpdateAction": {"@id": "schema:UpdateAction"}, + "Urologic": {"@id": "schema:Urologic"}, + "UsageOrScheduleHealthAspect": {"@id": "schema:UsageOrScheduleHealthAspect"}, + "UseAction": {"@id": "schema:UseAction"}, + "UsedCondition": {"@id": "schema:UsedCondition"}, + "UserBlocks": {"@id": "schema:UserBlocks"}, + "UserCheckins": {"@id": "schema:UserCheckins"}, + "UserComments": {"@id": "schema:UserComments"}, + "UserDownloads": {"@id": "schema:UserDownloads"}, + "UserInteraction": {"@id": "schema:UserInteraction"}, + "UserLikes": {"@id": "schema:UserLikes"}, + "UserPageVisits": {"@id": "schema:UserPageVisits"}, + "UserPlays": {"@id": "schema:UserPlays"}, + "UserPlusOnes": {"@id": "schema:UserPlusOnes"}, + "UserReview": {"@id": "schema:UserReview"}, + "UserTweets": {"@id": "schema:UserTweets"}, + "VeganDiet": {"@id": "schema:VeganDiet"}, + "VegetarianDiet": {"@id": "schema:VegetarianDiet"}, + "Vehicle": {"@id": "schema:Vehicle"}, + "Vein": {"@id": "schema:Vein"}, + "VenueMap": {"@id": "schema:VenueMap"}, + "Vessel": {"@id": "schema:Vessel"}, + "VeterinaryCare": {"@id": "schema:VeterinaryCare"}, + "VideoGallery": {"@id": "schema:VideoGallery"}, + "VideoGame": {"@id": "schema:VideoGame"}, + "VideoGameClip": {"@id": "schema:VideoGameClip"}, + "VideoGameSeries": {"@id": "schema:VideoGameSeries"}, + "VideoObject": {"@id": "schema:VideoObject"}, + "ViewAction": {"@id": "schema:ViewAction"}, + "VinylFormat": {"@id": "schema:VinylFormat"}, + "VirtualLocation": {"@id": "schema:VirtualLocation"}, + "Virus": {"@id": "schema:Virus"}, + "VisualArtsEvent": {"@id": "schema:VisualArtsEvent"}, + "VisualArtwork": {"@id": "schema:VisualArtwork"}, + "VitalSign": {"@id": "schema:VitalSign"}, + "Volcano": {"@id": "schema:Volcano"}, + "VoteAction": {"@id": "schema:VoteAction"}, + "WPAdBlock": {"@id": "schema:WPAdBlock"}, + "WPFooter": {"@id": "schema:WPFooter"}, + "WPHeader": {"@id": "schema:WPHeader"}, + "WPSideBar": {"@id": "schema:WPSideBar"}, + "WantAction": {"@id": "schema:WantAction"}, + "WarrantyPromise": {"@id": "schema:WarrantyPromise"}, + "WarrantyScope": {"@id": "schema:WarrantyScope"}, + "WatchAction": {"@id": "schema:WatchAction"}, + "Waterfall": {"@id": "schema:Waterfall"}, + "WearAction": {"@id": "schema:WearAction"}, + "WebAPI": {"@id": "schema:WebAPI"}, + "WebApplication": {"@id": "schema:WebApplication"}, + "WebContent": {"@id": "schema:WebContent"}, + "WebPage": {"@id": "schema:WebPage"}, + "WebPageElement": {"@id": "schema:WebPageElement"}, + "WebSite": {"@id": "schema:WebSite"}, + "Wednesday": {"@id": "schema:Wednesday"}, + "WesternConventional": {"@id": "schema:WesternConventional"}, + "Wholesale": {"@id": "schema:Wholesale"}, + "WholesaleStore": {"@id": "schema:WholesaleStore"}, + "WinAction": {"@id": "schema:WinAction"}, + "Winery": {"@id": "schema:Winery"}, + "Withdrawn": {"@id": "schema:Withdrawn"}, + "WorkBasedProgram": {"@id": "schema:WorkBasedProgram"}, + "WorkersUnion": {"@id": "schema:WorkersUnion"}, + "WriteAction": {"@id": "schema:WriteAction"}, + "WritePermission": {"@id": "schema:WritePermission"}, + "XPathType": {"@id": "schema:XPathType"}, + "XRay": {"@id": "schema:XRay"}, + "ZoneBoardingPolicy": {"@id": "schema:ZoneBoardingPolicy"}, + "Zoo": {"@id": "schema:Zoo"}, + "about": {"@id": "schema:about"}, + "abridged": {"@id": "schema:abridged"}, + "abstract": {"@id": "schema:abstract"}, + "accelerationTime": {"@id": "schema:accelerationTime"}, + "acceptedAnswer": {"@id": "schema:acceptedAnswer"}, + "acceptedOffer": {"@id": "schema:acceptedOffer"}, + "acceptedPaymentMethod": {"@id": "schema:acceptedPaymentMethod"}, + "acceptsReservations": {"@id": "schema:acceptsReservations"}, + "accessCode": {"@id": "schema:accessCode"}, + "accessMode": {"@id": "schema:accessMode"}, + "accessModeSufficient": {"@id": "schema:accessModeSufficient"}, + "accessibilityAPI": {"@id": "schema:accessibilityAPI"}, + "accessibilityControl": {"@id": "schema:accessibilityControl"}, + "accessibilityFeature": {"@id": "schema:accessibilityFeature"}, + "accessibilityHazard": {"@id": "schema:accessibilityHazard"}, + "accessibilitySummary": {"@id": "schema:accessibilitySummary"}, + "accommodationCategory": {"@id": "schema:accommodationCategory"}, + "accommodationFloorPlan": {"@id": "schema:accommodationFloorPlan"}, + "accountId": {"@id": "schema:accountId"}, + "accountMinimumInflow": {"@id": "schema:accountMinimumInflow"}, + "accountOverdraftLimit": {"@id": "schema:accountOverdraftLimit"}, + "accountablePerson": {"@id": "schema:accountablePerson"}, + "acquireLicensePage": {"@id": "schema:acquireLicensePage", "@type": "@id"}, + "acquiredFrom": {"@id": "schema:acquiredFrom"}, + "acrissCode": {"@id": "schema:acrissCode"}, + "actionAccessibilityRequirement": {"@id": "schema:actionAccessibilityRequirement"}, + "actionApplication": {"@id": "schema:actionApplication"}, + "actionOption": {"@id": "schema:actionOption"}, + "actionPlatform": {"@id": "schema:actionPlatform"}, + "actionStatus": {"@id": "schema:actionStatus"}, + "actionableFeedbackPolicy": { + "@id": "schema:actionableFeedbackPolicy", + "@type": "@id", + }, + "activeIngredient": {"@id": "schema:activeIngredient"}, + "activityDuration": {"@id": "schema:activityDuration"}, + "activityFrequency": {"@id": "schema:activityFrequency"}, + "actor": {"@id": "schema:actor"}, + "actors": {"@id": "schema:actors"}, + "addOn": {"@id": "schema:addOn"}, + "additionalName": {"@id": "schema:additionalName"}, + "additionalNumberOfGuests": {"@id": "schema:additionalNumberOfGuests"}, + "additionalProperty": {"@id": "schema:additionalProperty"}, + "additionalType": {"@id": "schema:additionalType", "@type": "@id"}, + "additionalVariable": {"@id": "schema:additionalVariable"}, + "address": {"@id": "schema:address"}, + "addressCountry": {"@id": "schema:addressCountry"}, + "addressLocality": {"@id": "schema:addressLocality"}, + "addressRegion": {"@id": "schema:addressRegion"}, + "administrationRoute": {"@id": "schema:administrationRoute"}, + "advanceBookingRequirement": {"@id": "schema:advanceBookingRequirement"}, + "adverseOutcome": {"@id": "schema:adverseOutcome"}, + "affectedBy": {"@id": "schema:affectedBy"}, + "affiliation": {"@id": "schema:affiliation"}, + "afterMedia": {"@id": "schema:afterMedia", "@type": "@id"}, + "agent": {"@id": "schema:agent"}, + "aggregateRating": {"@id": "schema:aggregateRating"}, + "aircraft": {"@id": "schema:aircraft"}, + "album": {"@id": "schema:album"}, + "albumProductionType": {"@id": "schema:albumProductionType"}, + "albumRelease": {"@id": "schema:albumRelease"}, + "albumReleaseType": {"@id": "schema:albumReleaseType"}, + "albums": {"@id": "schema:albums"}, + "alcoholWarning": {"@id": "schema:alcoholWarning"}, + "algorithm": {"@id": "schema:algorithm"}, + "alignmentType": {"@id": "schema:alignmentType"}, + "alternateName": {"@id": "schema:alternateName"}, + "alternativeHeadline": {"@id": "schema:alternativeHeadline"}, + "alumni": {"@id": "schema:alumni"}, + "alumniOf": {"@id": "schema:alumniOf"}, + "amenityFeature": {"@id": "schema:amenityFeature"}, + "amount": {"@id": "schema:amount"}, + "amountOfThisGood": {"@id": "schema:amountOfThisGood"}, + "annualPercentageRate": {"@id": "schema:annualPercentageRate"}, + "answerCount": {"@id": "schema:answerCount"}, + "antagonist": {"@id": "schema:antagonist"}, + "appearance": {"@id": "schema:appearance"}, + "applicableLocation": {"@id": "schema:applicableLocation"}, + "applicantLocationRequirements": {"@id": "schema:applicantLocationRequirements"}, + "application": {"@id": "schema:application"}, + "applicationCategory": {"@id": "schema:applicationCategory"}, + "applicationContact": {"@id": "schema:applicationContact"}, + "applicationDeadline": {"@id": "schema:applicationDeadline", "@type": "Date"}, + "applicationStartDate": {"@id": "schema:applicationStartDate", "@type": "Date"}, + "applicationSubCategory": {"@id": "schema:applicationSubCategory"}, + "applicationSuite": {"@id": "schema:applicationSuite"}, + "appliesToDeliveryMethod": {"@id": "schema:appliesToDeliveryMethod"}, + "appliesToPaymentMethod": {"@id": "schema:appliesToPaymentMethod"}, + "archiveHeld": {"@id": "schema:archiveHeld"}, + "area": {"@id": "schema:area"}, + "areaServed": {"@id": "schema:areaServed"}, + "arrivalAirport": {"@id": "schema:arrivalAirport"}, + "arrivalBusStop": {"@id": "schema:arrivalBusStop"}, + "arrivalGate": {"@id": "schema:arrivalGate"}, + "arrivalPlatform": {"@id": "schema:arrivalPlatform"}, + "arrivalStation": {"@id": "schema:arrivalStation"}, + "arrivalTerminal": {"@id": "schema:arrivalTerminal"}, + "arrivalTime": {"@id": "schema:arrivalTime", "@type": "DateTime"}, + "artEdition": {"@id": "schema:artEdition"}, + "artMedium": {"@id": "schema:artMedium"}, + "arterialBranch": {"@id": "schema:arterialBranch"}, + "artform": {"@id": "schema:artform"}, + "articleBody": {"@id": "schema:articleBody"}, + "articleSection": {"@id": "schema:articleSection"}, + "artist": {"@id": "schema:artist"}, + "artworkSurface": {"@id": "schema:artworkSurface"}, + "aspect": {"@id": "schema:aspect"}, + "assembly": {"@id": "schema:assembly"}, + "assemblyVersion": {"@id": "schema:assemblyVersion"}, + "associatedAnatomy": {"@id": "schema:associatedAnatomy"}, + "associatedArticle": {"@id": "schema:associatedArticle"}, + "associatedMedia": {"@id": "schema:associatedMedia"}, + "associatedPathophysiology": {"@id": "schema:associatedPathophysiology"}, + "athlete": {"@id": "schema:athlete"}, + "attendee": {"@id": "schema:attendee"}, + "attendees": {"@id": "schema:attendees"}, + "audience": {"@id": "schema:audience"}, + "audienceType": {"@id": "schema:audienceType"}, + "audio": {"@id": "schema:audio"}, + "authenticator": {"@id": "schema:authenticator"}, + "author": {"@id": "schema:author"}, + "availability": {"@id": "schema:availability"}, + "availabilityEnds": {"@id": "schema:availabilityEnds", "@type": "Date"}, + "availabilityStarts": {"@id": "schema:availabilityStarts", "@type": "Date"}, + "availableAtOrFrom": {"@id": "schema:availableAtOrFrom"}, + "availableChannel": {"@id": "schema:availableChannel"}, + "availableDeliveryMethod": {"@id": "schema:availableDeliveryMethod"}, + "availableFrom": {"@id": "schema:availableFrom", "@type": "DateTime"}, + "availableIn": {"@id": "schema:availableIn"}, + "availableLanguage": {"@id": "schema:availableLanguage"}, + "availableOnDevice": {"@id": "schema:availableOnDevice"}, + "availableService": {"@id": "schema:availableService"}, + "availableStrength": {"@id": "schema:availableStrength"}, + "availableTest": {"@id": "schema:availableTest"}, + "availableThrough": {"@id": "schema:availableThrough", "@type": "DateTime"}, + "award": {"@id": "schema:award"}, + "awards": {"@id": "schema:awards"}, + "awayTeam": {"@id": "schema:awayTeam"}, + "backstory": {"@id": "schema:backstory"}, + "bankAccountType": {"@id": "schema:bankAccountType"}, + "baseSalary": {"@id": "schema:baseSalary"}, + "bccRecipient": {"@id": "schema:bccRecipient"}, + "bed": {"@id": "schema:bed"}, + "beforeMedia": {"@id": "schema:beforeMedia", "@type": "@id"}, + "beneficiaryBank": {"@id": "schema:beneficiaryBank"}, + "benefits": {"@id": "schema:benefits"}, + "benefitsSummaryUrl": {"@id": "schema:benefitsSummaryUrl", "@type": "@id"}, + "bestRating": {"@id": "schema:bestRating"}, + "billingAddress": {"@id": "schema:billingAddress"}, + "billingIncrement": {"@id": "schema:billingIncrement"}, + "billingPeriod": {"@id": "schema:billingPeriod"}, + "biomechnicalClass": {"@id": "schema:biomechnicalClass"}, + "birthDate": {"@id": "schema:birthDate", "@type": "Date"}, + "birthPlace": {"@id": "schema:birthPlace"}, + "bitrate": {"@id": "schema:bitrate"}, + "blogPost": {"@id": "schema:blogPost"}, + "blogPosts": {"@id": "schema:blogPosts"}, + "bloodSupply": {"@id": "schema:bloodSupply"}, + "boardingGroup": {"@id": "schema:boardingGroup"}, + "boardingPolicy": {"@id": "schema:boardingPolicy"}, + "bodyLocation": {"@id": "schema:bodyLocation"}, + "bodyType": {"@id": "schema:bodyType"}, + "bookEdition": {"@id": "schema:bookEdition"}, + "bookFormat": {"@id": "schema:bookFormat"}, + "bookingAgent": {"@id": "schema:bookingAgent"}, + "bookingTime": {"@id": "schema:bookingTime", "@type": "DateTime"}, + "borrower": {"@id": "schema:borrower"}, + "box": {"@id": "schema:box"}, + "branch": {"@id": "schema:branch"}, + "branchCode": {"@id": "schema:branchCode"}, + "branchOf": {"@id": "schema:branchOf"}, + "brand": {"@id": "schema:brand"}, + "breadcrumb": {"@id": "schema:breadcrumb"}, + "breastfeedingWarning": {"@id": "schema:breastfeedingWarning"}, + "broadcastAffiliateOf": {"@id": "schema:broadcastAffiliateOf"}, + "broadcastChannelId": {"@id": "schema:broadcastChannelId"}, + "broadcastDisplayName": {"@id": "schema:broadcastDisplayName"}, + "broadcastFrequency": {"@id": "schema:broadcastFrequency"}, + "broadcastFrequencyValue": {"@id": "schema:broadcastFrequencyValue"}, + "broadcastOfEvent": {"@id": "schema:broadcastOfEvent"}, + "broadcastServiceTier": {"@id": "schema:broadcastServiceTier"}, + "broadcastSignalModulation": {"@id": "schema:broadcastSignalModulation"}, + "broadcastSubChannel": {"@id": "schema:broadcastSubChannel"}, + "broadcastTimezone": {"@id": "schema:broadcastTimezone"}, + "broadcaster": {"@id": "schema:broadcaster"}, + "broker": {"@id": "schema:broker"}, + "browserRequirements": {"@id": "schema:browserRequirements"}, + "busName": {"@id": "schema:busName"}, + "busNumber": {"@id": "schema:busNumber"}, + "businessFunction": {"@id": "schema:businessFunction"}, + "buyer": {"@id": "schema:buyer"}, + "byArtist": {"@id": "schema:byArtist"}, + "byDay": {"@id": "schema:byDay"}, + "byMonth": {"@id": "schema:byMonth"}, + "byMonthDay": {"@id": "schema:byMonthDay"}, + "callSign": {"@id": "schema:callSign"}, + "calories": {"@id": "schema:calories"}, + "candidate": {"@id": "schema:candidate"}, + "caption": {"@id": "schema:caption"}, + "carbohydrateContent": {"@id": "schema:carbohydrateContent"}, + "cargoVolume": {"@id": "schema:cargoVolume"}, + "carrier": {"@id": "schema:carrier"}, + "carrierRequirements": {"@id": "schema:carrierRequirements"}, + "cashBack": {"@id": "schema:cashBack"}, + "catalog": {"@id": "schema:catalog"}, + "catalogNumber": {"@id": "schema:catalogNumber"}, + "category": {"@id": "schema:category"}, + "causeOf": {"@id": "schema:causeOf"}, + "ccRecipient": {"@id": "schema:ccRecipient"}, + "character": {"@id": "schema:character"}, + "characterAttribute": {"@id": "schema:characterAttribute"}, + "characterName": {"@id": "schema:characterName"}, + "cheatCode": {"@id": "schema:cheatCode"}, + "checkinTime": {"@id": "schema:checkinTime", "@type": "DateTime"}, + "checkoutTime": {"@id": "schema:checkoutTime", "@type": "DateTime"}, + "childMaxAge": {"@id": "schema:childMaxAge"}, + "childMinAge": {"@id": "schema:childMinAge"}, + "children": {"@id": "schema:children"}, + "cholesterolContent": {"@id": "schema:cholesterolContent"}, + "circle": {"@id": "schema:circle"}, + "citation": {"@id": "schema:citation"}, + "claimReviewed": {"@id": "schema:claimReviewed"}, + "clincalPharmacology": {"@id": "schema:clincalPharmacology"}, + "clinicalPharmacology": {"@id": "schema:clinicalPharmacology"}, + "clipNumber": {"@id": "schema:clipNumber"}, + "closes": {"@id": "schema:closes"}, + "coach": {"@id": "schema:coach"}, + "code": {"@id": "schema:code"}, + "codeRepository": {"@id": "schema:codeRepository", "@type": "@id"}, + "codeSampleType": {"@id": "schema:codeSampleType"}, + "codeValue": {"@id": "schema:codeValue"}, + "codingSystem": {"@id": "schema:codingSystem"}, + "colleague": {"@id": "schema:colleague", "@type": "@id"}, + "colleagues": {"@id": "schema:colleagues"}, + "collection": {"@id": "schema:collection"}, + "collectionSize": {"@id": "schema:collectionSize"}, + "color": {"@id": "schema:color"}, + "colorist": {"@id": "schema:colorist"}, + "comment": {"@id": "schema:comment"}, + "commentCount": {"@id": "schema:commentCount"}, + "commentText": {"@id": "schema:commentText"}, + "commentTime": {"@id": "schema:commentTime", "@type": "Date"}, + "competencyRequired": {"@id": "schema:competencyRequired"}, + "competitor": {"@id": "schema:competitor"}, + "composer": {"@id": "schema:composer"}, + "comprisedOf": {"@id": "schema:comprisedOf"}, + "conditionsOfAccess": {"@id": "schema:conditionsOfAccess"}, + "confirmationNumber": {"@id": "schema:confirmationNumber"}, + "connectedTo": {"@id": "schema:connectedTo"}, + "constrainingProperty": {"@id": "schema:constrainingProperty"}, + "contactOption": {"@id": "schema:contactOption"}, + "contactPoint": {"@id": "schema:contactPoint"}, + "contactPoints": {"@id": "schema:contactPoints"}, + "contactType": {"@id": "schema:contactType"}, + "contactlessPayment": {"@id": "schema:contactlessPayment"}, + "containedIn": {"@id": "schema:containedIn"}, + "containedInPlace": {"@id": "schema:containedInPlace"}, + "containsPlace": {"@id": "schema:containsPlace"}, + "containsSeason": {"@id": "schema:containsSeason"}, + "contentLocation": {"@id": "schema:contentLocation"}, + "contentRating": {"@id": "schema:contentRating"}, + "contentReferenceTime": { + "@id": "schema:contentReferenceTime", + "@type": "DateTime", + }, + "contentSize": {"@id": "schema:contentSize"}, + "contentType": {"@id": "schema:contentType"}, + "contentUrl": {"@id": "schema:contentUrl", "@type": "@id"}, + "contraindication": {"@id": "schema:contraindication"}, + "contributor": {"@id": "schema:contributor"}, + "cookTime": {"@id": "schema:cookTime"}, + "cookingMethod": {"@id": "schema:cookingMethod"}, + "copyrightHolder": {"@id": "schema:copyrightHolder"}, + "copyrightYear": {"@id": "schema:copyrightYear"}, + "correction": {"@id": "schema:correction"}, + "correctionsPolicy": {"@id": "schema:correctionsPolicy", "@type": "@id"}, + "costCategory": {"@id": "schema:costCategory"}, + "costCurrency": {"@id": "schema:costCurrency"}, + "costOrigin": {"@id": "schema:costOrigin"}, + "costPerUnit": {"@id": "schema:costPerUnit"}, + "countriesNotSupported": {"@id": "schema:countriesNotSupported"}, + "countriesSupported": {"@id": "schema:countriesSupported"}, + "countryOfOrigin": {"@id": "schema:countryOfOrigin"}, + "course": {"@id": "schema:course"}, + "courseCode": {"@id": "schema:courseCode"}, + "courseMode": {"@id": "schema:courseMode"}, + "coursePrerequisites": {"@id": "schema:coursePrerequisites"}, + "courseWorkload": {"@id": "schema:courseWorkload"}, + "coverageEndTime": {"@id": "schema:coverageEndTime", "@type": "DateTime"}, + "coverageStartTime": {"@id": "schema:coverageStartTime", "@type": "DateTime"}, + "creativeWorkStatus": {"@id": "schema:creativeWorkStatus"}, + "creator": {"@id": "schema:creator"}, + "credentialCategory": {"@id": "schema:credentialCategory"}, + "creditedTo": {"@id": "schema:creditedTo"}, + "cssSelector": {"@id": "schema:cssSelector"}, + "currenciesAccepted": {"@id": "schema:currenciesAccepted"}, + "currency": {"@id": "schema:currency"}, + "currentExchangeRate": {"@id": "schema:currentExchangeRate"}, + "customer": {"@id": "schema:customer"}, + "dataFeedElement": {"@id": "schema:dataFeedElement"}, + "dataset": {"@id": "schema:dataset"}, + "datasetTimeInterval": {"@id": "schema:datasetTimeInterval", "@type": "DateTime"}, + "dateCreated": {"@id": "schema:dateCreated", "@type": "Date"}, + "dateDeleted": {"@id": "schema:dateDeleted", "@type": "Date"}, + "dateIssued": {"@id": "schema:dateIssued", "@type": "Date"}, + "dateModified": {"@id": "schema:dateModified", "@type": "Date"}, + "datePosted": {"@id": "schema:datePosted", "@type": "Date"}, + "datePublished": {"@id": "schema:datePublished", "@type": "Date"}, + "dateRead": {"@id": "schema:dateRead", "@type": "Date"}, + "dateReceived": {"@id": "schema:dateReceived", "@type": "DateTime"}, + "dateSent": {"@id": "schema:dateSent", "@type": "DateTime"}, + "dateVehicleFirstRegistered": { + "@id": "schema:dateVehicleFirstRegistered", + "@type": "Date", + }, + "dateline": {"@id": "schema:dateline"}, + "dayOfWeek": {"@id": "schema:dayOfWeek"}, + "deathDate": {"@id": "schema:deathDate", "@type": "Date"}, + "deathPlace": {"@id": "schema:deathPlace"}, + "defaultValue": {"@id": "schema:defaultValue"}, + "deliveryAddress": {"@id": "schema:deliveryAddress"}, + "deliveryLeadTime": {"@id": "schema:deliveryLeadTime"}, + "deliveryMethod": {"@id": "schema:deliveryMethod"}, + "deliveryStatus": {"@id": "schema:deliveryStatus"}, + "department": {"@id": "schema:department"}, + "departureAirport": {"@id": "schema:departureAirport"}, + "departureBusStop": {"@id": "schema:departureBusStop"}, + "departureGate": {"@id": "schema:departureGate"}, + "departurePlatform": {"@id": "schema:departurePlatform"}, + "departureStation": {"@id": "schema:departureStation"}, + "departureTerminal": {"@id": "schema:departureTerminal"}, + "departureTime": {"@id": "schema:departureTime", "@type": "DateTime"}, + "dependencies": {"@id": "schema:dependencies"}, + "depth": {"@id": "schema:depth"}, + "description": {"@id": "schema:description"}, + "device": {"@id": "schema:device"}, + "diagnosis": {"@id": "schema:diagnosis"}, + "diagram": {"@id": "schema:diagram"}, + "diet": {"@id": "schema:diet"}, + "dietFeatures": {"@id": "schema:dietFeatures"}, + "differentialDiagnosis": {"@id": "schema:differentialDiagnosis"}, + "director": {"@id": "schema:director"}, + "directors": {"@id": "schema:directors"}, + "disambiguatingDescription": {"@id": "schema:disambiguatingDescription"}, + "discount": {"@id": "schema:discount"}, + "discountCode": {"@id": "schema:discountCode"}, + "discountCurrency": {"@id": "schema:discountCurrency"}, + "discusses": {"@id": "schema:discusses"}, + "discussionUrl": {"@id": "schema:discussionUrl", "@type": "@id"}, + "diseasePreventionInfo": {"@id": "schema:diseasePreventionInfo", "@type": "@id"}, + "diseaseSpreadStatistics": { + "@id": "schema:diseaseSpreadStatistics", + "@type": "@id", + }, + "dissolutionDate": {"@id": "schema:dissolutionDate", "@type": "Date"}, + "distance": {"@id": "schema:distance"}, + "distinguishingSign": {"@id": "schema:distinguishingSign"}, + "distribution": {"@id": "schema:distribution"}, + "diversityPolicy": {"@id": "schema:diversityPolicy", "@type": "@id"}, + "diversityStaffingReport": { + "@id": "schema:diversityStaffingReport", + "@type": "@id", + }, + "documentation": {"@id": "schema:documentation", "@type": "@id"}, + "domainIncludes": {"@id": "schema:domainIncludes"}, + "domiciledMortgage": {"@id": "schema:domiciledMortgage"}, + "doorTime": {"@id": "schema:doorTime", "@type": "DateTime"}, + "dosageForm": {"@id": "schema:dosageForm"}, + "doseSchedule": {"@id": "schema:doseSchedule"}, + "doseUnit": {"@id": "schema:doseUnit"}, + "doseValue": {"@id": "schema:doseValue"}, + "downPayment": {"@id": "schema:downPayment"}, + "downloadUrl": {"@id": "schema:downloadUrl", "@type": "@id"}, + "downvoteCount": {"@id": "schema:downvoteCount"}, + "drainsTo": {"@id": "schema:drainsTo"}, + "driveWheelConfiguration": {"@id": "schema:driveWheelConfiguration"}, + "dropoffLocation": {"@id": "schema:dropoffLocation"}, + "dropoffTime": {"@id": "schema:dropoffTime", "@type": "DateTime"}, + "drug": {"@id": "schema:drug"}, + "drugClass": {"@id": "schema:drugClass"}, + "drugUnit": {"@id": "schema:drugUnit"}, + "duns": {"@id": "schema:duns"}, + "duplicateTherapy": {"@id": "schema:duplicateTherapy"}, + "duration": {"@id": "schema:duration"}, + "durationOfWarranty": {"@id": "schema:durationOfWarranty"}, + "duringMedia": {"@id": "schema:duringMedia", "@type": "@id"}, + "earlyPrepaymentPenalty": {"@id": "schema:earlyPrepaymentPenalty"}, + "editor": {"@id": "schema:editor"}, + "educationRequirements": {"@id": "schema:educationRequirements"}, + "educationalAlignment": {"@id": "schema:educationalAlignment"}, + "educationalCredentialAwarded": {"@id": "schema:educationalCredentialAwarded"}, + "educationalFramework": {"@id": "schema:educationalFramework"}, + "educationalLevel": {"@id": "schema:educationalLevel"}, + "educationalProgramMode": {"@id": "schema:educationalProgramMode"}, + "educationalRole": {"@id": "schema:educationalRole"}, + "educationalUse": {"@id": "schema:educationalUse"}, + "elevation": {"@id": "schema:elevation"}, + "eligibleCustomerType": {"@id": "schema:eligibleCustomerType"}, + "eligibleDuration": {"@id": "schema:eligibleDuration"}, + "eligibleQuantity": {"@id": "schema:eligibleQuantity"}, + "eligibleRegion": {"@id": "schema:eligibleRegion"}, + "eligibleTransactionVolume": {"@id": "schema:eligibleTransactionVolume"}, + "email": {"@id": "schema:email"}, + "embedUrl": {"@id": "schema:embedUrl", "@type": "@id"}, + "emissionsCO2": {"@id": "schema:emissionsCO2"}, + "employee": {"@id": "schema:employee"}, + "employees": {"@id": "schema:employees"}, + "employerOverview": {"@id": "schema:employerOverview"}, + "employmentType": {"@id": "schema:employmentType"}, + "employmentUnit": {"@id": "schema:employmentUnit"}, + "encodesCreativeWork": {"@id": "schema:encodesCreativeWork"}, + "encoding": {"@id": "schema:encoding"}, + "encodingFormat": {"@id": "schema:encodingFormat"}, + "encodingType": {"@id": "schema:encodingType"}, + "encodings": {"@id": "schema:encodings"}, + "endDate": {"@id": "schema:endDate", "@type": "Date"}, + "endOffset": {"@id": "schema:endOffset"}, + "endTime": {"@id": "schema:endTime", "@type": "DateTime"}, + "endorsee": {"@id": "schema:endorsee"}, + "endorsers": {"@id": "schema:endorsers"}, + "engineDisplacement": {"@id": "schema:engineDisplacement"}, + "enginePower": {"@id": "schema:enginePower"}, + "engineType": {"@id": "schema:engineType"}, + "entertainmentBusiness": {"@id": "schema:entertainmentBusiness"}, + "epidemiology": {"@id": "schema:epidemiology"}, + "episode": {"@id": "schema:episode"}, + "episodeNumber": {"@id": "schema:episodeNumber"}, + "episodes": {"@id": "schema:episodes"}, + "equal": {"@id": "schema:equal"}, + "error": {"@id": "schema:error"}, + "estimatedCost": {"@id": "schema:estimatedCost"}, + "estimatedFlightDuration": {"@id": "schema:estimatedFlightDuration"}, + "estimatedSalary": {"@id": "schema:estimatedSalary"}, + "estimatesRiskOf": {"@id": "schema:estimatesRiskOf"}, + "ethicsPolicy": {"@id": "schema:ethicsPolicy", "@type": "@id"}, + "event": {"@id": "schema:event"}, + "eventAttendanceMode": {"@id": "schema:eventAttendanceMode"}, + "eventSchedule": {"@id": "schema:eventSchedule"}, + "eventStatus": {"@id": "schema:eventStatus"}, + "events": {"@id": "schema:events"}, + "evidenceLevel": {"@id": "schema:evidenceLevel"}, + "evidenceOrigin": {"@id": "schema:evidenceOrigin"}, + "exampleOfWork": {"@id": "schema:exampleOfWork"}, + "exceptDate": {"@id": "schema:exceptDate", "@type": "Date"}, + "exchangeRateSpread": {"@id": "schema:exchangeRateSpread"}, + "executableLibraryName": {"@id": "schema:executableLibraryName"}, + "exerciseCourse": {"@id": "schema:exerciseCourse"}, + "exercisePlan": {"@id": "schema:exercisePlan"}, + "exerciseRelatedDiet": {"@id": "schema:exerciseRelatedDiet"}, + "exerciseType": {"@id": "schema:exerciseType"}, + "exifData": {"@id": "schema:exifData"}, + "expectedArrivalFrom": {"@id": "schema:expectedArrivalFrom", "@type": "Date"}, + "expectedArrivalUntil": {"@id": "schema:expectedArrivalUntil", "@type": "Date"}, + "expectedPrognosis": {"@id": "schema:expectedPrognosis"}, + "expectsAcceptanceOf": {"@id": "schema:expectsAcceptanceOf"}, + "experienceRequirements": {"@id": "schema:experienceRequirements"}, + "expertConsiderations": {"@id": "schema:expertConsiderations"}, + "expires": {"@id": "schema:expires", "@type": "Date"}, + "familyName": {"@id": "schema:familyName"}, + "fatContent": {"@id": "schema:fatContent"}, + "faxNumber": {"@id": "schema:faxNumber"}, + "featureList": {"@id": "schema:featureList"}, + "feesAndCommissionsSpecification": { + "@id": "schema:feesAndCommissionsSpecification" + }, + "fiberContent": {"@id": "schema:fiberContent"}, + "fileFormat": {"@id": "schema:fileFormat"}, + "fileSize": {"@id": "schema:fileSize"}, + "financialAidEligible": {"@id": "schema:financialAidEligible"}, + "firstAppearance": {"@id": "schema:firstAppearance"}, + "firstPerformance": {"@id": "schema:firstPerformance"}, + "flightDistance": {"@id": "schema:flightDistance"}, + "flightNumber": {"@id": "schema:flightNumber"}, + "floorLevel": {"@id": "schema:floorLevel"}, + "floorLimit": {"@id": "schema:floorLimit"}, + "floorSize": {"@id": "schema:floorSize"}, + "followee": {"@id": "schema:followee"}, + "follows": {"@id": "schema:follows"}, + "followup": {"@id": "schema:followup"}, + "foodEstablishment": {"@id": "schema:foodEstablishment"}, + "foodEvent": {"@id": "schema:foodEvent"}, + "foodWarning": {"@id": "schema:foodWarning"}, + "founder": {"@id": "schema:founder"}, + "founders": {"@id": "schema:founders"}, + "foundingDate": {"@id": "schema:foundingDate", "@type": "Date"}, + "foundingLocation": {"@id": "schema:foundingLocation"}, + "free": {"@id": "schema:free"}, + "frequency": {"@id": "schema:frequency"}, + "fromLocation": {"@id": "schema:fromLocation"}, + "fuelCapacity": {"@id": "schema:fuelCapacity"}, + "fuelConsumption": {"@id": "schema:fuelConsumption"}, + "fuelEfficiency": {"@id": "schema:fuelEfficiency"}, + "fuelType": {"@id": "schema:fuelType"}, + "functionalClass": {"@id": "schema:functionalClass"}, + "fundedItem": {"@id": "schema:fundedItem"}, + "funder": {"@id": "schema:funder"}, + "game": {"@id": "schema:game"}, + "gameItem": {"@id": "schema:gameItem"}, + "gameLocation": {"@id": "schema:gameLocation", "@type": "@id"}, + "gamePlatform": {"@id": "schema:gamePlatform"}, + "gameServer": {"@id": "schema:gameServer"}, + "gameTip": {"@id": "schema:gameTip"}, + "gender": {"@id": "schema:gender"}, + "genre": {"@id": "schema:genre"}, + "geo": {"@id": "schema:geo"}, + "geoContains": {"@id": "schema:geoContains"}, + "geoCoveredBy": {"@id": "schema:geoCoveredBy"}, + "geoCovers": {"@id": "schema:geoCovers"}, + "geoCrosses": {"@id": "schema:geoCrosses"}, + "geoDisjoint": {"@id": "schema:geoDisjoint"}, + "geoEquals": {"@id": "schema:geoEquals"}, + "geoIntersects": {"@id": "schema:geoIntersects"}, + "geoMidpoint": {"@id": "schema:geoMidpoint"}, + "geoOverlaps": {"@id": "schema:geoOverlaps"}, + "geoRadius": {"@id": "schema:geoRadius"}, + "geoTouches": {"@id": "schema:geoTouches"}, + "geoWithin": {"@id": "schema:geoWithin"}, + "geographicArea": {"@id": "schema:geographicArea"}, + "gettingTestedInfo": {"@id": "schema:gettingTestedInfo", "@type": "@id"}, + "givenName": {"@id": "schema:givenName"}, + "globalLocationNumber": {"@id": "schema:globalLocationNumber"}, + "gracePeriod": {"@id": "schema:gracePeriod"}, + "grantee": {"@id": "schema:grantee"}, + "greater": {"@id": "schema:greater"}, + "greaterOrEqual": {"@id": "schema:greaterOrEqual"}, + "gtin": {"@id": "schema:gtin"}, + "gtin12": {"@id": "schema:gtin12"}, + "gtin13": {"@id": "schema:gtin13"}, + "gtin14": {"@id": "schema:gtin14"}, + "gtin8": {"@id": "schema:gtin8"}, + "guideline": {"@id": "schema:guideline"}, + "guidelineDate": {"@id": "schema:guidelineDate", "@type": "Date"}, + "guidelineSubject": {"@id": "schema:guidelineSubject"}, + "hasBroadcastChannel": {"@id": "schema:hasBroadcastChannel"}, + "hasCategoryCode": {"@id": "schema:hasCategoryCode"}, + "hasCourseInstance": {"@id": "schema:hasCourseInstance"}, + "hasCredential": {"@id": "schema:hasCredential"}, + "hasDefinedTerm": {"@id": "schema:hasDefinedTerm"}, + "hasDeliveryMethod": {"@id": "schema:hasDeliveryMethod"}, + "hasDigitalDocumentPermission": {"@id": "schema:hasDigitalDocumentPermission"}, + "hasDriveThroughService": {"@id": "schema:hasDriveThroughService"}, + "hasHealthAspect": {"@id": "schema:hasHealthAspect"}, + "hasMap": {"@id": "schema:hasMap", "@type": "@id"}, + "hasMenu": {"@id": "schema:hasMenu"}, + "hasMenuItem": {"@id": "schema:hasMenuItem"}, + "hasMenuSection": {"@id": "schema:hasMenuSection"}, + "hasMerchantReturnPolicy": {"@id": "schema:hasMerchantReturnPolicy"}, + "hasOccupation": {"@id": "schema:hasOccupation"}, + "hasOfferCatalog": {"@id": "schema:hasOfferCatalog"}, + "hasPOS": {"@id": "schema:hasPOS"}, + "hasPart": {"@id": "schema:hasPart"}, + "hasProductReturnPolicy": {"@id": "schema:hasProductReturnPolicy"}, + "headline": {"@id": "schema:headline"}, + "healthCondition": {"@id": "schema:healthCondition"}, + "healthPlanCoinsuranceOption": {"@id": "schema:healthPlanCoinsuranceOption"}, + "healthPlanCoinsuranceRate": {"@id": "schema:healthPlanCoinsuranceRate"}, + "healthPlanCopay": {"@id": "schema:healthPlanCopay"}, + "healthPlanCopayOption": {"@id": "schema:healthPlanCopayOption"}, + "healthPlanCostSharing": {"@id": "schema:healthPlanCostSharing"}, + "healthPlanDrugOption": {"@id": "schema:healthPlanDrugOption"}, + "healthPlanDrugTier": {"@id": "schema:healthPlanDrugTier"}, + "healthPlanId": {"@id": "schema:healthPlanId"}, + "healthPlanMarketingUrl": {"@id": "schema:healthPlanMarketingUrl", "@type": "@id"}, + "healthPlanNetworkId": {"@id": "schema:healthPlanNetworkId"}, + "healthPlanNetworkTier": {"@id": "schema:healthPlanNetworkTier"}, + "healthPlanPharmacyCategory": {"@id": "schema:healthPlanPharmacyCategory"}, + "height": {"@id": "schema:height"}, + "highPrice": {"@id": "schema:highPrice"}, + "hiringOrganization": {"@id": "schema:hiringOrganization"}, + "holdingArchive": {"@id": "schema:holdingArchive"}, + "homeLocation": {"@id": "schema:homeLocation"}, + "homeTeam": {"@id": "schema:homeTeam"}, + "honorificPrefix": {"@id": "schema:honorificPrefix"}, + "honorificSuffix": {"@id": "schema:honorificSuffix"}, + "hospitalAffiliation": {"@id": "schema:hospitalAffiliation"}, + "hostingOrganization": {"@id": "schema:hostingOrganization"}, + "hoursAvailable": {"@id": "schema:hoursAvailable"}, + "howPerformed": {"@id": "schema:howPerformed"}, + "iataCode": {"@id": "schema:iataCode"}, + "icaoCode": {"@id": "schema:icaoCode"}, + "identifier": {"@id": "schema:identifier"}, + "identifyingExam": {"@id": "schema:identifyingExam"}, + "identifyingTest": {"@id": "schema:identifyingTest"}, + "illustrator": {"@id": "schema:illustrator"}, + "image": {"@id": "schema:image", "@type": "@id"}, + "imagingTechnique": {"@id": "schema:imagingTechnique"}, + "inAlbum": {"@id": "schema:inAlbum"}, + "inBroadcastLineup": {"@id": "schema:inBroadcastLineup"}, + "inCodeSet": {"@id": "schema:inCodeSet", "@type": "@id"}, + "inDefinedTermSet": {"@id": "schema:inDefinedTermSet", "@type": "@id"}, + "inLanguage": {"@id": "schema:inLanguage"}, + "inPlaylist": {"@id": "schema:inPlaylist"}, + "inStoreReturnsOffered": {"@id": "schema:inStoreReturnsOffered"}, + "inSupportOf": {"@id": "schema:inSupportOf"}, + "incentiveCompensation": {"@id": "schema:incentiveCompensation"}, + "incentives": {"@id": "schema:incentives"}, + "includedComposition": {"@id": "schema:includedComposition"}, + "includedDataCatalog": {"@id": "schema:includedDataCatalog"}, + "includedInDataCatalog": {"@id": "schema:includedInDataCatalog"}, + "includedInHealthInsurancePlan": {"@id": "schema:includedInHealthInsurancePlan"}, + "includedRiskFactor": {"@id": "schema:includedRiskFactor"}, + "includesAttraction": {"@id": "schema:includesAttraction"}, + "includesHealthPlanFormulary": {"@id": "schema:includesHealthPlanFormulary"}, + "includesHealthPlanNetwork": {"@id": "schema:includesHealthPlanNetwork"}, + "includesObject": {"@id": "schema:includesObject"}, + "increasesRiskOf": {"@id": "schema:increasesRiskOf"}, + "industry": {"@id": "schema:industry"}, + "ineligibleRegion": {"@id": "schema:ineligibleRegion"}, + "infectiousAgent": {"@id": "schema:infectiousAgent"}, + "infectiousAgentClass": {"@id": "schema:infectiousAgentClass"}, + "ingredients": {"@id": "schema:ingredients"}, + "inker": {"@id": "schema:inker"}, + "insertion": {"@id": "schema:insertion"}, + "installUrl": {"@id": "schema:installUrl", "@type": "@id"}, + "instructor": {"@id": "schema:instructor"}, + "instrument": {"@id": "schema:instrument"}, + "intensity": {"@id": "schema:intensity"}, + "interactingDrug": {"@id": "schema:interactingDrug"}, + "interactionCount": {"@id": "schema:interactionCount"}, + "interactionService": {"@id": "schema:interactionService"}, + "interactionStatistic": {"@id": "schema:interactionStatistic"}, + "interactionType": {"@id": "schema:interactionType"}, + "interactivityType": {"@id": "schema:interactivityType"}, + "interestRate": {"@id": "schema:interestRate"}, + "inventoryLevel": {"@id": "schema:inventoryLevel"}, + "inverseOf": {"@id": "schema:inverseOf"}, + "isAcceptingNewPatients": {"@id": "schema:isAcceptingNewPatients"}, + "isAccessibleForFree": {"@id": "schema:isAccessibleForFree"}, + "isAccessoryOrSparePartFor": {"@id": "schema:isAccessoryOrSparePartFor"}, + "isAvailableGenerically": {"@id": "schema:isAvailableGenerically"}, + "isBasedOn": {"@id": "schema:isBasedOn", "@type": "@id"}, + "isBasedOnUrl": {"@id": "schema:isBasedOnUrl", "@type": "@id"}, + "isConsumableFor": {"@id": "schema:isConsumableFor"}, + "isFamilyFriendly": {"@id": "schema:isFamilyFriendly"}, + "isGift": {"@id": "schema:isGift"}, + "isLiveBroadcast": {"@id": "schema:isLiveBroadcast"}, + "isPartOf": {"@id": "schema:isPartOf", "@type": "@id"}, + "isPlanForApartment": {"@id": "schema:isPlanForApartment"}, + "isProprietary": {"@id": "schema:isProprietary"}, + "isRelatedTo": {"@id": "schema:isRelatedTo"}, + "isResizable": {"@id": "schema:isResizable"}, + "isSimilarTo": {"@id": "schema:isSimilarTo"}, + "isVariantOf": {"@id": "schema:isVariantOf"}, + "isbn": {"@id": "schema:isbn"}, + "isicV4": {"@id": "schema:isicV4"}, + "isrcCode": {"@id": "schema:isrcCode"}, + "issn": {"@id": "schema:issn"}, + "issueNumber": {"@id": "schema:issueNumber"}, + "issuedBy": {"@id": "schema:issuedBy"}, + "issuedThrough": {"@id": "schema:issuedThrough"}, + "iswcCode": {"@id": "schema:iswcCode"}, + "item": {"@id": "schema:item"}, + "itemCondition": {"@id": "schema:itemCondition"}, + "itemListElement": {"@id": "schema:itemListElement"}, + "itemListOrder": {"@id": "schema:itemListOrder"}, + "itemLocation": {"@id": "schema:itemLocation"}, + "itemOffered": {"@id": "schema:itemOffered"}, + "itemReviewed": {"@id": "schema:itemReviewed"}, + "itemShipped": {"@id": "schema:itemShipped"}, + "itinerary": {"@id": "schema:itinerary"}, + "jobBenefits": {"@id": "schema:jobBenefits"}, + "jobImmediateStart": {"@id": "schema:jobImmediateStart"}, + "jobLocation": {"@id": "schema:jobLocation"}, + "jobLocationType": {"@id": "schema:jobLocationType"}, + "jobStartDate": {"@id": "schema:jobStartDate"}, + "jobTitle": {"@id": "schema:jobTitle"}, + "keywords": {"@id": "schema:keywords"}, + "knownVehicleDamages": {"@id": "schema:knownVehicleDamages"}, + "knows": {"@id": "schema:knows"}, + "knowsAbout": {"@id": "schema:knowsAbout"}, + "knowsLanguage": {"@id": "schema:knowsLanguage"}, + "labelDetails": {"@id": "schema:labelDetails", "@type": "@id"}, + "landlord": {"@id": "schema:landlord"}, + "language": {"@id": "schema:language"}, + "lastReviewed": {"@id": "schema:lastReviewed", "@type": "Date"}, + "latitude": {"@id": "schema:latitude"}, + "learningResourceType": {"@id": "schema:learningResourceType"}, + "leaseLength": {"@id": "schema:leaseLength"}, + "legalName": {"@id": "schema:legalName"}, + "legalStatus": {"@id": "schema:legalStatus"}, + "legislationApplies": {"@id": "schema:legislationApplies"}, + "legislationChanges": {"@id": "schema:legislationChanges"}, + "legislationConsolidates": {"@id": "schema:legislationConsolidates"}, + "legislationDate": {"@id": "schema:legislationDate", "@type": "Date"}, + "legislationDateVersion": { + "@id": "schema:legislationDateVersion", + "@type": "Date", + }, + "legislationIdentifier": {"@id": "schema:legislationIdentifier"}, + "legislationJurisdiction": {"@id": "schema:legislationJurisdiction"}, + "legislationLegalForce": {"@id": "schema:legislationLegalForce"}, + "legislationLegalValue": {"@id": "schema:legislationLegalValue"}, + "legislationPassedBy": {"@id": "schema:legislationPassedBy"}, + "legislationResponsible": {"@id": "schema:legislationResponsible"}, + "legislationTransposes": {"@id": "schema:legislationTransposes"}, + "legislationType": {"@id": "schema:legislationType"}, + "leiCode": {"@id": "schema:leiCode"}, + "lender": {"@id": "schema:lender"}, + "lesser": {"@id": "schema:lesser"}, + "lesserOrEqual": {"@id": "schema:lesserOrEqual"}, + "letterer": {"@id": "schema:letterer"}, + "license": {"@id": "schema:license", "@type": "@id"}, + "line": {"@id": "schema:line"}, + "linkRelationship": {"@id": "schema:linkRelationship"}, + "liveBlogUpdate": {"@id": "schema:liveBlogUpdate"}, + "loanMortgageMandateAmount": {"@id": "schema:loanMortgageMandateAmount"}, + "loanPaymentAmount": {"@id": "schema:loanPaymentAmount"}, + "loanPaymentFrequency": {"@id": "schema:loanPaymentFrequency"}, + "loanRepaymentForm": {"@id": "schema:loanRepaymentForm"}, + "loanTerm": {"@id": "schema:loanTerm"}, + "loanType": {"@id": "schema:loanType"}, + "location": {"@id": "schema:location"}, + "locationCreated": {"@id": "schema:locationCreated"}, + "lodgingUnitDescription": {"@id": "schema:lodgingUnitDescription"}, + "lodgingUnitType": {"@id": "schema:lodgingUnitType"}, + "logo": {"@id": "schema:logo", "@type": "@id"}, + "longitude": {"@id": "schema:longitude"}, + "loser": {"@id": "schema:loser"}, + "lowPrice": {"@id": "schema:lowPrice"}, + "lyricist": {"@id": "schema:lyricist"}, + "lyrics": {"@id": "schema:lyrics"}, + "mainContentOfPage": {"@id": "schema:mainContentOfPage"}, + "mainEntity": {"@id": "schema:mainEntity"}, + "mainEntityOfPage": {"@id": "schema:mainEntityOfPage", "@type": "@id"}, + "maintainer": {"@id": "schema:maintainer"}, + "makesOffer": {"@id": "schema:makesOffer"}, + "manufacturer": {"@id": "schema:manufacturer"}, + "map": {"@id": "schema:map", "@type": "@id"}, + "mapType": {"@id": "schema:mapType"}, + "maps": {"@id": "schema:maps", "@type": "@id"}, + "marginOfError": {"@id": "schema:marginOfError", "@type": "DateTime"}, + "masthead": {"@id": "schema:masthead", "@type": "@id"}, + "material": {"@id": "schema:material"}, + "materialExtent": {"@id": "schema:materialExtent"}, + "maxPrice": {"@id": "schema:maxPrice"}, + "maxValue": {"@id": "schema:maxValue"}, + "maximumAttendeeCapacity": {"@id": "schema:maximumAttendeeCapacity"}, + "maximumEnrollment": {"@id": "schema:maximumEnrollment"}, + "maximumIntake": {"@id": "schema:maximumIntake"}, + "maximumPhysicalAttendeeCapacity": { + "@id": "schema:maximumPhysicalAttendeeCapacity" + }, + "maximumVirtualAttendeeCapacity": {"@id": "schema:maximumVirtualAttendeeCapacity"}, + "mealService": {"@id": "schema:mealService"}, + "measuredProperty": {"@id": "schema:measuredProperty"}, + "measuredValue": {"@id": "schema:measuredValue"}, + "measurementTechnique": {"@id": "schema:measurementTechnique"}, + "mechanismOfAction": {"@id": "schema:mechanismOfAction"}, + "mediaAuthenticityCategory": {"@id": "schema:mediaAuthenticityCategory"}, + "median": {"@id": "schema:median"}, + "medicalSpecialty": {"@id": "schema:medicalSpecialty"}, + "medicineSystem": {"@id": "schema:medicineSystem"}, + "meetsEmissionStandard": {"@id": "schema:meetsEmissionStandard"}, + "member": {"@id": "schema:member"}, + "memberOf": {"@id": "schema:memberOf"}, + "members": {"@id": "schema:members"}, + "membershipNumber": {"@id": "schema:membershipNumber"}, + "membershipPointsEarned": {"@id": "schema:membershipPointsEarned"}, + "memoryRequirements": {"@id": "schema:memoryRequirements"}, + "mentions": {"@id": "schema:mentions"}, + "menu": {"@id": "schema:menu"}, + "menuAddOn": {"@id": "schema:menuAddOn"}, + "merchant": {"@id": "schema:merchant"}, + "merchantReturnDays": {"@id": "schema:merchantReturnDays"}, + "merchantReturnLink": {"@id": "schema:merchantReturnLink", "@type": "@id"}, + "messageAttachment": {"@id": "schema:messageAttachment"}, + "mileageFromOdometer": {"@id": "schema:mileageFromOdometer"}, + "minPrice": {"@id": "schema:minPrice"}, + "minValue": {"@id": "schema:minValue"}, + "minimumPaymentDue": {"@id": "schema:minimumPaymentDue"}, + "missionCoveragePrioritiesPolicy": { + "@id": "schema:missionCoveragePrioritiesPolicy", + "@type": "@id", + }, + "model": {"@id": "schema:model"}, + "modelDate": {"@id": "schema:modelDate", "@type": "Date"}, + "modifiedTime": {"@id": "schema:modifiedTime", "@type": "DateTime"}, + "monthlyMinimumRepaymentAmount": {"@id": "schema:monthlyMinimumRepaymentAmount"}, + "mpn": {"@id": "schema:mpn"}, + "multipleValues": {"@id": "schema:multipleValues"}, + "muscleAction": {"@id": "schema:muscleAction"}, + "musicArrangement": {"@id": "schema:musicArrangement"}, + "musicBy": {"@id": "schema:musicBy"}, + "musicCompositionForm": {"@id": "schema:musicCompositionForm"}, + "musicGroupMember": {"@id": "schema:musicGroupMember"}, + "musicReleaseFormat": {"@id": "schema:musicReleaseFormat"}, + "musicalKey": {"@id": "schema:musicalKey"}, + "naics": {"@id": "schema:naics"}, + "name": {"@id": "schema:name"}, + "namedPosition": {"@id": "schema:namedPosition"}, + "nationality": {"@id": "schema:nationality"}, + "naturalProgression": {"@id": "schema:naturalProgression"}, + "nerve": {"@id": "schema:nerve"}, + "nerveMotor": {"@id": "schema:nerveMotor"}, + "netWorth": {"@id": "schema:netWorth"}, + "newsUpdatesAndGuidelines": { + "@id": "schema:newsUpdatesAndGuidelines", + "@type": "@id", + }, + "nextItem": {"@id": "schema:nextItem"}, + "noBylinesPolicy": {"@id": "schema:noBylinesPolicy", "@type": "@id"}, + "nonEqual": {"@id": "schema:nonEqual"}, + "nonProprietaryName": {"@id": "schema:nonProprietaryName"}, + "normalRange": {"@id": "schema:normalRange"}, + "nsn": {"@id": "schema:nsn"}, + "numAdults": {"@id": "schema:numAdults"}, + "numChildren": {"@id": "schema:numChildren"}, + "numConstraints": {"@id": "schema:numConstraints"}, + "numTracks": {"@id": "schema:numTracks"}, + "numberOfAccommodationUnits": {"@id": "schema:numberOfAccommodationUnits"}, + "numberOfAirbags": {"@id": "schema:numberOfAirbags"}, + "numberOfAvailableAccommodationUnits": { + "@id": "schema:numberOfAvailableAccommodationUnits" + }, + "numberOfAxles": {"@id": "schema:numberOfAxles"}, + "numberOfBathroomsTotal": {"@id": "schema:numberOfBathroomsTotal"}, + "numberOfBedrooms": {"@id": "schema:numberOfBedrooms"}, + "numberOfBeds": {"@id": "schema:numberOfBeds"}, + "numberOfCredits": {"@id": "schema:numberOfCredits"}, + "numberOfDoors": {"@id": "schema:numberOfDoors"}, + "numberOfEmployees": {"@id": "schema:numberOfEmployees"}, + "numberOfEpisodes": {"@id": "schema:numberOfEpisodes"}, + "numberOfForwardGears": {"@id": "schema:numberOfForwardGears"}, + "numberOfFullBathrooms": {"@id": "schema:numberOfFullBathrooms"}, + "numberOfItems": {"@id": "schema:numberOfItems"}, + "numberOfLoanPayments": {"@id": "schema:numberOfLoanPayments"}, + "numberOfPages": {"@id": "schema:numberOfPages"}, + "numberOfPartialBathrooms": {"@id": "schema:numberOfPartialBathrooms"}, + "numberOfPlayers": {"@id": "schema:numberOfPlayers"}, + "numberOfPreviousOwners": {"@id": "schema:numberOfPreviousOwners"}, + "numberOfRooms": {"@id": "schema:numberOfRooms"}, + "numberOfSeasons": {"@id": "schema:numberOfSeasons"}, + "numberedPosition": {"@id": "schema:numberedPosition"}, + "nutrition": {"@id": "schema:nutrition"}, + "object": {"@id": "schema:object"}, + "observationDate": {"@id": "schema:observationDate", "@type": "DateTime"}, + "observedNode": {"@id": "schema:observedNode"}, + "occupancy": {"@id": "schema:occupancy"}, + "occupationLocation": {"@id": "schema:occupationLocation"}, + "occupationalCategory": {"@id": "schema:occupationalCategory"}, + "occupationalCredentialAwarded": {"@id": "schema:occupationalCredentialAwarded"}, + "offerCount": {"@id": "schema:offerCount"}, + "offeredBy": {"@id": "schema:offeredBy"}, + "offers": {"@id": "schema:offers"}, + "offersPrescriptionByMail": {"@id": "schema:offersPrescriptionByMail"}, + "openingHours": {"@id": "schema:openingHours"}, + "openingHoursSpecification": {"@id": "schema:openingHoursSpecification"}, + "opens": {"@id": "schema:opens"}, + "operatingSystem": {"@id": "schema:operatingSystem"}, + "opponent": {"@id": "schema:opponent"}, + "option": {"@id": "schema:option"}, + "orderDate": {"@id": "schema:orderDate", "@type": "Date"}, + "orderDelivery": {"@id": "schema:orderDelivery"}, + "orderItemNumber": {"@id": "schema:orderItemNumber"}, + "orderItemStatus": {"@id": "schema:orderItemStatus"}, + "orderNumber": {"@id": "schema:orderNumber"}, + "orderQuantity": {"@id": "schema:orderQuantity"}, + "orderStatus": {"@id": "schema:orderStatus"}, + "orderedItem": {"@id": "schema:orderedItem"}, + "organizer": {"@id": "schema:organizer"}, + "originAddress": {"@id": "schema:originAddress"}, + "originatesFrom": {"@id": "schema:originatesFrom"}, + "overdosage": {"@id": "schema:overdosage"}, + "ownedFrom": {"@id": "schema:ownedFrom", "@type": "DateTime"}, + "ownedThrough": {"@id": "schema:ownedThrough", "@type": "DateTime"}, + "ownershipFundingInfo": {"@id": "schema:ownershipFundingInfo"}, + "owns": {"@id": "schema:owns"}, + "pageEnd": {"@id": "schema:pageEnd"}, + "pageStart": {"@id": "schema:pageStart"}, + "pagination": {"@id": "schema:pagination"}, + "parent": {"@id": "schema:parent"}, + "parentItem": {"@id": "schema:parentItem"}, + "parentOrganization": {"@id": "schema:parentOrganization"}, + "parentService": {"@id": "schema:parentService"}, + "parents": {"@id": "schema:parents"}, + "partOfEpisode": {"@id": "schema:partOfEpisode"}, + "partOfInvoice": {"@id": "schema:partOfInvoice"}, + "partOfOrder": {"@id": "schema:partOfOrder"}, + "partOfSeason": {"@id": "schema:partOfSeason"}, + "partOfSeries": {"@id": "schema:partOfSeries"}, + "partOfSystem": {"@id": "schema:partOfSystem"}, + "partOfTVSeries": {"@id": "schema:partOfTVSeries"}, + "partOfTrip": {"@id": "schema:partOfTrip"}, + "participant": {"@id": "schema:participant"}, + "partySize": {"@id": "schema:partySize"}, + "passengerPriorityStatus": {"@id": "schema:passengerPriorityStatus"}, + "passengerSequenceNumber": {"@id": "schema:passengerSequenceNumber"}, + "pathophysiology": {"@id": "schema:pathophysiology"}, + "payload": {"@id": "schema:payload"}, + "paymentAccepted": {"@id": "schema:paymentAccepted"}, + "paymentDue": {"@id": "schema:paymentDue", "@type": "DateTime"}, + "paymentDueDate": {"@id": "schema:paymentDueDate", "@type": "Date"}, + "paymentMethod": {"@id": "schema:paymentMethod"}, + "paymentMethodId": {"@id": "schema:paymentMethodId"}, + "paymentStatus": {"@id": "schema:paymentStatus"}, + "paymentUrl": {"@id": "schema:paymentUrl", "@type": "@id"}, + "penciler": {"@id": "schema:penciler"}, + "percentile10": {"@id": "schema:percentile10"}, + "percentile25": {"@id": "schema:percentile25"}, + "percentile75": {"@id": "schema:percentile75"}, + "percentile90": {"@id": "schema:percentile90"}, + "performTime": {"@id": "schema:performTime"}, + "performer": {"@id": "schema:performer"}, + "performerIn": {"@id": "schema:performerIn"}, + "performers": {"@id": "schema:performers"}, + "permissionType": {"@id": "schema:permissionType"}, + "permissions": {"@id": "schema:permissions"}, + "permitAudience": {"@id": "schema:permitAudience"}, + "permittedUsage": {"@id": "schema:permittedUsage"}, + "petsAllowed": {"@id": "schema:petsAllowed"}, + "phoneticText": {"@id": "schema:phoneticText"}, + "photo": {"@id": "schema:photo"}, + "photos": {"@id": "schema:photos"}, + "physicalRequirement": {"@id": "schema:physicalRequirement"}, + "physiologicalBenefits": {"@id": "schema:physiologicalBenefits"}, + "pickupLocation": {"@id": "schema:pickupLocation"}, + "pickupTime": {"@id": "schema:pickupTime", "@type": "DateTime"}, + "playMode": {"@id": "schema:playMode"}, + "playerType": {"@id": "schema:playerType"}, + "playersOnline": {"@id": "schema:playersOnline"}, + "polygon": {"@id": "schema:polygon"}, + "populationType": {"@id": "schema:populationType"}, + "position": {"@id": "schema:position"}, + "possibleComplication": {"@id": "schema:possibleComplication"}, + "possibleTreatment": {"@id": "schema:possibleTreatment"}, + "postOfficeBoxNumber": {"@id": "schema:postOfficeBoxNumber"}, + "postOp": {"@id": "schema:postOp"}, + "postalCode": {"@id": "schema:postalCode"}, + "potentialAction": {"@id": "schema:potentialAction"}, + "preOp": {"@id": "schema:preOp"}, + "predecessorOf": {"@id": "schema:predecessorOf"}, + "pregnancyCategory": {"@id": "schema:pregnancyCategory"}, + "pregnancyWarning": {"@id": "schema:pregnancyWarning"}, + "prepTime": {"@id": "schema:prepTime"}, + "preparation": {"@id": "schema:preparation"}, + "prescribingInfo": {"@id": "schema:prescribingInfo", "@type": "@id"}, + "prescriptionStatus": {"@id": "schema:prescriptionStatus"}, + "previousItem": {"@id": "schema:previousItem"}, + "previousStartDate": {"@id": "schema:previousStartDate", "@type": "Date"}, + "price": {"@id": "schema:price"}, + "priceComponent": {"@id": "schema:priceComponent"}, + "priceCurrency": {"@id": "schema:priceCurrency"}, + "priceRange": {"@id": "schema:priceRange"}, + "priceSpecification": {"@id": "schema:priceSpecification"}, + "priceType": {"@id": "schema:priceType"}, + "priceValidUntil": {"@id": "schema:priceValidUntil", "@type": "Date"}, + "primaryImageOfPage": {"@id": "schema:primaryImageOfPage"}, + "primaryPrevention": {"@id": "schema:primaryPrevention"}, + "printColumn": {"@id": "schema:printColumn"}, + "printEdition": {"@id": "schema:printEdition"}, + "printPage": {"@id": "schema:printPage"}, + "printSection": {"@id": "schema:printSection"}, + "procedure": {"@id": "schema:procedure"}, + "procedureType": {"@id": "schema:procedureType"}, + "processingTime": {"@id": "schema:processingTime"}, + "processorRequirements": {"@id": "schema:processorRequirements"}, + "producer": {"@id": "schema:producer"}, + "produces": {"@id": "schema:produces"}, + "productID": {"@id": "schema:productID"}, + "productReturnDays": {"@id": "schema:productReturnDays"}, + "productReturnLink": {"@id": "schema:productReturnLink", "@type": "@id"}, + "productSupported": {"@id": "schema:productSupported"}, + "productionCompany": {"@id": "schema:productionCompany"}, + "productionDate": {"@id": "schema:productionDate", "@type": "Date"}, + "proficiencyLevel": {"@id": "schema:proficiencyLevel"}, + "programMembershipUsed": {"@id": "schema:programMembershipUsed"}, + "programName": {"@id": "schema:programName"}, + "programPrerequisites": {"@id": "schema:programPrerequisites"}, + "programType": {"@id": "schema:programType"}, + "programmingLanguage": {"@id": "schema:programmingLanguage"}, + "programmingModel": {"@id": "schema:programmingModel"}, + "propertyID": {"@id": "schema:propertyID"}, + "proprietaryName": {"@id": "schema:proprietaryName"}, + "proteinContent": {"@id": "schema:proteinContent"}, + "provider": {"@id": "schema:provider"}, + "providerMobility": {"@id": "schema:providerMobility"}, + "providesBroadcastService": {"@id": "schema:providesBroadcastService"}, + "providesService": {"@id": "schema:providesService"}, + "publicAccess": {"@id": "schema:publicAccess"}, + "publicTransportClosuresInfo": { + "@id": "schema:publicTransportClosuresInfo", + "@type": "@id", + }, + "publication": {"@id": "schema:publication"}, + "publicationType": {"@id": "schema:publicationType"}, + "publishedBy": {"@id": "schema:publishedBy"}, + "publishedOn": {"@id": "schema:publishedOn"}, + "publisher": {"@id": "schema:publisher"}, + "publisherImprint": {"@id": "schema:publisherImprint"}, + "publishingPrinciples": {"@id": "schema:publishingPrinciples", "@type": "@id"}, + "purchaseDate": {"@id": "schema:purchaseDate", "@type": "Date"}, + "qualifications": {"@id": "schema:qualifications"}, + "quarantineGuidelines": {"@id": "schema:quarantineGuidelines", "@type": "@id"}, + "query": {"@id": "schema:query"}, + "quest": {"@id": "schema:quest"}, + "question": {"@id": "schema:question"}, + "rangeIncludes": {"@id": "schema:rangeIncludes"}, + "ratingCount": {"@id": "schema:ratingCount"}, + "ratingExplanation": {"@id": "schema:ratingExplanation"}, + "ratingValue": {"@id": "schema:ratingValue"}, + "readBy": {"@id": "schema:readBy"}, + "readonlyValue": {"@id": "schema:readonlyValue"}, + "realEstateAgent": {"@id": "schema:realEstateAgent"}, + "recipe": {"@id": "schema:recipe"}, + "recipeCategory": {"@id": "schema:recipeCategory"}, + "recipeCuisine": {"@id": "schema:recipeCuisine"}, + "recipeIngredient": {"@id": "schema:recipeIngredient"}, + "recipeInstructions": {"@id": "schema:recipeInstructions"}, + "recipeYield": {"@id": "schema:recipeYield"}, + "recipient": {"@id": "schema:recipient"}, + "recognizedBy": {"@id": "schema:recognizedBy"}, + "recognizingAuthority": {"@id": "schema:recognizingAuthority"}, + "recommendationStrength": {"@id": "schema:recommendationStrength"}, + "recommendedIntake": {"@id": "schema:recommendedIntake"}, + "recordLabel": {"@id": "schema:recordLabel"}, + "recordedAs": {"@id": "schema:recordedAs"}, + "recordedAt": {"@id": "schema:recordedAt"}, + "recordedIn": {"@id": "schema:recordedIn"}, + "recordingOf": {"@id": "schema:recordingOf"}, + "recourseLoan": {"@id": "schema:recourseLoan"}, + "referenceQuantity": {"@id": "schema:referenceQuantity"}, + "referencesOrder": {"@id": "schema:referencesOrder"}, + "refundType": {"@id": "schema:refundType"}, + "regionDrained": {"@id": "schema:regionDrained"}, + "regionsAllowed": {"@id": "schema:regionsAllowed"}, + "relatedAnatomy": {"@id": "schema:relatedAnatomy"}, + "relatedCondition": {"@id": "schema:relatedCondition"}, + "relatedDrug": {"@id": "schema:relatedDrug"}, + "relatedLink": {"@id": "schema:relatedLink", "@type": "@id"}, + "relatedStructure": {"@id": "schema:relatedStructure"}, + "relatedTherapy": {"@id": "schema:relatedTherapy"}, + "relatedTo": {"@id": "schema:relatedTo"}, + "releaseDate": {"@id": "schema:releaseDate", "@type": "Date"}, + "releaseNotes": {"@id": "schema:releaseNotes"}, + "releaseOf": {"@id": "schema:releaseOf"}, + "releasedEvent": {"@id": "schema:releasedEvent"}, + "relevantOccupation": {"@id": "schema:relevantOccupation"}, + "relevantSpecialty": {"@id": "schema:relevantSpecialty"}, + "remainingAttendeeCapacity": {"@id": "schema:remainingAttendeeCapacity"}, + "renegotiableLoan": {"@id": "schema:renegotiableLoan"}, + "repeatCount": {"@id": "schema:repeatCount"}, + "repeatFrequency": {"@id": "schema:repeatFrequency"}, + "repetitions": {"@id": "schema:repetitions"}, + "replacee": {"@id": "schema:replacee"}, + "replacer": {"@id": "schema:replacer"}, + "replyToUrl": {"@id": "schema:replyToUrl", "@type": "@id"}, + "reportNumber": {"@id": "schema:reportNumber"}, + "representativeOfPage": {"@id": "schema:representativeOfPage"}, + "requiredCollateral": {"@id": "schema:requiredCollateral"}, + "requiredGender": {"@id": "schema:requiredGender"}, + "requiredMaxAge": {"@id": "schema:requiredMaxAge"}, + "requiredMinAge": {"@id": "schema:requiredMinAge"}, + "requiredQuantity": {"@id": "schema:requiredQuantity"}, + "requirements": {"@id": "schema:requirements"}, + "requiresSubscription": {"@id": "schema:requiresSubscription"}, + "reservationFor": {"@id": "schema:reservationFor"}, + "reservationId": {"@id": "schema:reservationId"}, + "reservationStatus": {"@id": "schema:reservationStatus"}, + "reservedTicket": {"@id": "schema:reservedTicket"}, + "responsibilities": {"@id": "schema:responsibilities"}, + "restPeriods": {"@id": "schema:restPeriods"}, + "result": {"@id": "schema:result"}, + "resultComment": {"@id": "schema:resultComment"}, + "resultReview": {"@id": "schema:resultReview"}, + "returnFees": {"@id": "schema:returnFees"}, + "returnPolicyCategory": {"@id": "schema:returnPolicyCategory"}, + "review": {"@id": "schema:review"}, + "reviewAspect": {"@id": "schema:reviewAspect"}, + "reviewBody": {"@id": "schema:reviewBody"}, + "reviewCount": {"@id": "schema:reviewCount"}, + "reviewRating": {"@id": "schema:reviewRating"}, + "reviewedBy": {"@id": "schema:reviewedBy"}, + "reviews": {"@id": "schema:reviews"}, + "riskFactor": {"@id": "schema:riskFactor"}, + "risks": {"@id": "schema:risks"}, + "roleName": {"@id": "schema:roleName"}, + "roofLoad": {"@id": "schema:roofLoad"}, + "rsvpResponse": {"@id": "schema:rsvpResponse"}, + "runsTo": {"@id": "schema:runsTo"}, + "runtime": {"@id": "schema:runtime"}, + "runtimePlatform": {"@id": "schema:runtimePlatform"}, + "rxcui": {"@id": "schema:rxcui"}, + "safetyConsideration": {"@id": "schema:safetyConsideration"}, + "salaryCurrency": {"@id": "schema:salaryCurrency"}, + "salaryUponCompletion": {"@id": "schema:salaryUponCompletion"}, + "sameAs": {"@id": "schema:sameAs", "@type": "@id"}, + "sampleType": {"@id": "schema:sampleType"}, + "saturatedFatContent": {"@id": "schema:saturatedFatContent"}, + "scheduleTimezone": {"@id": "schema:scheduleTimezone"}, + "scheduledPaymentDate": {"@id": "schema:scheduledPaymentDate", "@type": "Date"}, + "scheduledTime": {"@id": "schema:scheduledTime", "@type": "DateTime"}, + "schemaVersion": {"@id": "schema:schemaVersion"}, + "schoolClosuresInfo": {"@id": "schema:schoolClosuresInfo", "@type": "@id"}, + "screenCount": {"@id": "schema:screenCount"}, + "screenshot": {"@id": "schema:screenshot", "@type": "@id"}, + "sdDatePublished": {"@id": "schema:sdDatePublished", "@type": "Date"}, + "sdLicense": {"@id": "schema:sdLicense", "@type": "@id"}, + "sdPublisher": {"@id": "schema:sdPublisher"}, + "season": {"@id": "schema:season", "@type": "@id"}, + "seasonNumber": {"@id": "schema:seasonNumber"}, + "seasons": {"@id": "schema:seasons"}, + "seatNumber": {"@id": "schema:seatNumber"}, + "seatRow": {"@id": "schema:seatRow"}, + "seatSection": {"@id": "schema:seatSection"}, + "seatingCapacity": {"@id": "schema:seatingCapacity"}, + "seatingType": {"@id": "schema:seatingType"}, + "secondaryPrevention": {"@id": "schema:secondaryPrevention"}, + "securityClearanceRequirement": {"@id": "schema:securityClearanceRequirement"}, + "securityScreening": {"@id": "schema:securityScreening"}, + "seeks": {"@id": "schema:seeks"}, + "seller": {"@id": "schema:seller"}, + "sender": {"@id": "schema:sender"}, + "sensoryRequirement": {"@id": "schema:sensoryRequirement"}, + "sensoryUnit": {"@id": "schema:sensoryUnit"}, + "serialNumber": {"@id": "schema:serialNumber"}, + "seriousAdverseOutcome": {"@id": "schema:seriousAdverseOutcome"}, + "serverStatus": {"@id": "schema:serverStatus"}, + "servesCuisine": {"@id": "schema:servesCuisine"}, + "serviceArea": {"@id": "schema:serviceArea"}, + "serviceAudience": {"@id": "schema:serviceAudience"}, + "serviceLocation": {"@id": "schema:serviceLocation"}, + "serviceOperator": {"@id": "schema:serviceOperator"}, + "serviceOutput": {"@id": "schema:serviceOutput"}, + "servicePhone": {"@id": "schema:servicePhone"}, + "servicePostalAddress": {"@id": "schema:servicePostalAddress"}, + "serviceSmsNumber": {"@id": "schema:serviceSmsNumber"}, + "serviceType": {"@id": "schema:serviceType"}, + "serviceUrl": {"@id": "schema:serviceUrl", "@type": "@id"}, + "servingSize": {"@id": "schema:servingSize"}, + "sharedContent": {"@id": "schema:sharedContent"}, + "sibling": {"@id": "schema:sibling"}, + "siblings": {"@id": "schema:siblings"}, + "signDetected": {"@id": "schema:signDetected"}, + "signOrSymptom": {"@id": "schema:signOrSymptom"}, + "significance": {"@id": "schema:significance"}, + "significantLink": {"@id": "schema:significantLink", "@type": "@id"}, + "significantLinks": {"@id": "schema:significantLinks", "@type": "@id"}, + "skills": {"@id": "schema:skills"}, + "sku": {"@id": "schema:sku"}, + "slogan": {"@id": "schema:slogan"}, + "smokingAllowed": {"@id": "schema:smokingAllowed"}, + "sodiumContent": {"@id": "schema:sodiumContent"}, + "softwareAddOn": {"@id": "schema:softwareAddOn"}, + "softwareHelp": {"@id": "schema:softwareHelp"}, + "softwareRequirements": {"@id": "schema:softwareRequirements"}, + "softwareVersion": {"@id": "schema:softwareVersion"}, + "sourceOrganization": {"@id": "schema:sourceOrganization"}, + "sourcedFrom": {"@id": "schema:sourcedFrom"}, + "spatial": {"@id": "schema:spatial"}, + "spatialCoverage": {"@id": "schema:spatialCoverage"}, + "speakable": {"@id": "schema:speakable", "@type": "@id"}, + "specialCommitments": {"@id": "schema:specialCommitments"}, + "specialOpeningHoursSpecification": { + "@id": "schema:specialOpeningHoursSpecification" + }, + "specialty": {"@id": "schema:specialty"}, + "speechToTextMarkup": {"@id": "schema:speechToTextMarkup"}, + "speed": {"@id": "schema:speed"}, + "spokenByCharacter": {"@id": "schema:spokenByCharacter"}, + "sponsor": {"@id": "schema:sponsor"}, + "sport": {"@id": "schema:sport"}, + "sportsActivityLocation": {"@id": "schema:sportsActivityLocation"}, + "sportsEvent": {"@id": "schema:sportsEvent"}, + "sportsTeam": {"@id": "schema:sportsTeam"}, + "spouse": {"@id": "schema:spouse"}, + "stage": {"@id": "schema:stage"}, + "stageAsNumber": {"@id": "schema:stageAsNumber"}, + "starRating": {"@id": "schema:starRating"}, + "startDate": {"@id": "schema:startDate", "@type": "Date"}, + "startOffset": {"@id": "schema:startOffset"}, + "startTime": {"@id": "schema:startTime", "@type": "DateTime"}, + "status": {"@id": "schema:status"}, + "steeringPosition": {"@id": "schema:steeringPosition"}, + "step": {"@id": "schema:step"}, + "stepValue": {"@id": "schema:stepValue"}, + "steps": {"@id": "schema:steps"}, + "storageRequirements": {"@id": "schema:storageRequirements"}, + "streetAddress": {"@id": "schema:streetAddress"}, + "strengthUnit": {"@id": "schema:strengthUnit"}, + "strengthValue": {"@id": "schema:strengthValue"}, + "structuralClass": {"@id": "schema:structuralClass"}, + "study": {"@id": "schema:study"}, + "studyDesign": {"@id": "schema:studyDesign"}, + "studyLocation": {"@id": "schema:studyLocation"}, + "studySubject": {"@id": "schema:studySubject"}, + "stupidProperty": {"@id": "schema:stupidProperty"}, + "subEvent": {"@id": "schema:subEvent"}, + "subEvents": {"@id": "schema:subEvents"}, + "subOrganization": {"@id": "schema:subOrganization"}, + "subReservation": {"@id": "schema:subReservation"}, + "subStageSuffix": {"@id": "schema:subStageSuffix"}, + "subStructure": {"@id": "schema:subStructure"}, + "subTest": {"@id": "schema:subTest"}, + "subTrip": {"@id": "schema:subTrip"}, + "subjectOf": {"@id": "schema:subjectOf"}, + "subtitleLanguage": {"@id": "schema:subtitleLanguage"}, + "successorOf": {"@id": "schema:successorOf"}, + "sugarContent": {"@id": "schema:sugarContent"}, + "suggestedAnswer": {"@id": "schema:suggestedAnswer"}, + "suggestedGender": {"@id": "schema:suggestedGender"}, + "suggestedMaxAge": {"@id": "schema:suggestedMaxAge"}, + "suggestedMinAge": {"@id": "schema:suggestedMinAge"}, + "suitableForDiet": {"@id": "schema:suitableForDiet"}, + "superEvent": {"@id": "schema:superEvent"}, + "supersededBy": {"@id": "schema:supersededBy"}, + "supply": {"@id": "schema:supply"}, + "supplyTo": {"@id": "schema:supplyTo"}, + "supportingData": {"@id": "schema:supportingData"}, + "surface": {"@id": "schema:surface"}, + "target": {"@id": "schema:target"}, + "targetCollection": {"@id": "schema:targetCollection"}, + "targetDescription": {"@id": "schema:targetDescription"}, + "targetName": {"@id": "schema:targetName"}, + "targetPlatform": {"@id": "schema:targetPlatform"}, + "targetPopulation": {"@id": "schema:targetPopulation"}, + "targetProduct": {"@id": "schema:targetProduct"}, + "targetUrl": {"@id": "schema:targetUrl", "@type": "@id"}, + "taxID": {"@id": "schema:taxID"}, + "telephone": {"@id": "schema:telephone"}, + "temporal": {"@id": "schema:temporal"}, + "temporalCoverage": {"@id": "schema:temporalCoverage"}, + "termCode": {"@id": "schema:termCode"}, + "termDuration": {"@id": "schema:termDuration"}, + "termsOfService": {"@id": "schema:termsOfService"}, + "termsPerYear": {"@id": "schema:termsPerYear"}, + "text": {"@id": "schema:text"}, + "textValue": {"@id": "schema:textValue"}, + "thumbnail": {"@id": "schema:thumbnail"}, + "thumbnailUrl": {"@id": "schema:thumbnailUrl", "@type": "@id"}, + "tickerSymbol": {"@id": "schema:tickerSymbol"}, + "ticketNumber": {"@id": "schema:ticketNumber"}, + "ticketToken": {"@id": "schema:ticketToken"}, + "ticketedSeat": {"@id": "schema:ticketedSeat"}, + "timeOfDay": {"@id": "schema:timeOfDay"}, + "timeRequired": {"@id": "schema:timeRequired"}, + "timeToComplete": {"@id": "schema:timeToComplete"}, + "tissueSample": {"@id": "schema:tissueSample"}, + "title": {"@id": "schema:title"}, + "toLocation": {"@id": "schema:toLocation"}, + "toRecipient": {"@id": "schema:toRecipient"}, + "tongueWeight": {"@id": "schema:tongueWeight"}, + "tool": {"@id": "schema:tool"}, + "torque": {"@id": "schema:torque"}, + "totalJobOpenings": {"@id": "schema:totalJobOpenings"}, + "totalPaymentDue": {"@id": "schema:totalPaymentDue"}, + "totalPrice": {"@id": "schema:totalPrice"}, + "totalTime": {"@id": "schema:totalTime"}, + "tourBookingPage": {"@id": "schema:tourBookingPage", "@type": "@id"}, + "touristType": {"@id": "schema:touristType"}, + "track": {"@id": "schema:track"}, + "trackingNumber": {"@id": "schema:trackingNumber"}, + "trackingUrl": {"@id": "schema:trackingUrl", "@type": "@id"}, + "tracks": {"@id": "schema:tracks"}, + "trailer": {"@id": "schema:trailer"}, + "trailerWeight": {"@id": "schema:trailerWeight"}, + "trainName": {"@id": "schema:trainName"}, + "trainNumber": {"@id": "schema:trainNumber"}, + "trainingSalary": {"@id": "schema:trainingSalary"}, + "transFatContent": {"@id": "schema:transFatContent"}, + "transcript": {"@id": "schema:transcript"}, + "translationOfWork": {"@id": "schema:translationOfWork"}, + "translator": {"@id": "schema:translator"}, + "transmissionMethod": {"@id": "schema:transmissionMethod"}, + "travelBans": {"@id": "schema:travelBans", "@type": "@id"}, + "trialDesign": {"@id": "schema:trialDesign"}, + "tributary": {"@id": "schema:tributary"}, + "typeOfBed": {"@id": "schema:typeOfBed"}, + "typeOfGood": {"@id": "schema:typeOfGood"}, + "typicalAgeRange": {"@id": "schema:typicalAgeRange"}, + "typicalCreditsPerTerm": {"@id": "schema:typicalCreditsPerTerm"}, + "typicalTest": {"@id": "schema:typicalTest"}, + "underName": {"@id": "schema:underName"}, + "unitCode": {"@id": "schema:unitCode"}, + "unitText": {"@id": "schema:unitText"}, + "unnamedSourcesPolicy": {"@id": "schema:unnamedSourcesPolicy", "@type": "@id"}, + "unsaturatedFatContent": {"@id": "schema:unsaturatedFatContent"}, + "uploadDate": {"@id": "schema:uploadDate", "@type": "Date"}, + "upvoteCount": {"@id": "schema:upvoteCount"}, + "url": {"@id": "schema:url", "@type": "@id"}, + "urlTemplate": {"@id": "schema:urlTemplate"}, + "usageInfo": {"@id": "schema:usageInfo", "@type": "@id"}, + "usedToDiagnose": {"@id": "schema:usedToDiagnose"}, + "userInteractionCount": {"@id": "schema:userInteractionCount"}, + "usesDevice": {"@id": "schema:usesDevice"}, + "usesHealthPlanIdStandard": {"@id": "schema:usesHealthPlanIdStandard"}, + "validFor": {"@id": "schema:validFor"}, + "validFrom": {"@id": "schema:validFrom", "@type": "Date"}, + "validIn": {"@id": "schema:validIn"}, + "validThrough": {"@id": "schema:validThrough", "@type": "Date"}, + "validUntil": {"@id": "schema:validUntil", "@type": "Date"}, + "value": {"@id": "schema:value"}, + "valueAddedTaxIncluded": {"@id": "schema:valueAddedTaxIncluded"}, + "valueMaxLength": {"@id": "schema:valueMaxLength"}, + "valueMinLength": {"@id": "schema:valueMinLength"}, + "valueName": {"@id": "schema:valueName"}, + "valuePattern": {"@id": "schema:valuePattern"}, + "valueReference": {"@id": "schema:valueReference"}, + "valueRequired": {"@id": "schema:valueRequired"}, + "variableMeasured": {"@id": "schema:variableMeasured"}, + "variablesMeasured": {"@id": "schema:variablesMeasured"}, + "variantCover": {"@id": "schema:variantCover"}, + "vatID": {"@id": "schema:vatID"}, + "vehicleConfiguration": {"@id": "schema:vehicleConfiguration"}, + "vehicleEngine": {"@id": "schema:vehicleEngine"}, + "vehicleIdentificationNumber": {"@id": "schema:vehicleIdentificationNumber"}, + "vehicleInteriorColor": {"@id": "schema:vehicleInteriorColor"}, + "vehicleInteriorType": {"@id": "schema:vehicleInteriorType"}, + "vehicleModelDate": {"@id": "schema:vehicleModelDate", "@type": "Date"}, + "vehicleSeatingCapacity": {"@id": "schema:vehicleSeatingCapacity"}, + "vehicleSpecialUsage": {"@id": "schema:vehicleSpecialUsage"}, + "vehicleTransmission": {"@id": "schema:vehicleTransmission"}, + "vendor": {"@id": "schema:vendor"}, + "verificationFactCheckingPolicy": { + "@id": "schema:verificationFactCheckingPolicy", + "@type": "@id", + }, + "version": {"@id": "schema:version"}, + "video": {"@id": "schema:video"}, + "videoFormat": {"@id": "schema:videoFormat"}, + "videoFrameSize": {"@id": "schema:videoFrameSize"}, + "videoQuality": {"@id": "schema:videoQuality"}, + "volumeNumber": {"@id": "schema:volumeNumber"}, + "warning": {"@id": "schema:warning"}, + "warranty": {"@id": "schema:warranty"}, + "warrantyPromise": {"@id": "schema:warrantyPromise"}, + "warrantyScope": {"@id": "schema:warrantyScope"}, + "webCheckinTime": {"@id": "schema:webCheckinTime", "@type": "DateTime"}, + "webFeed": {"@id": "schema:webFeed", "@type": "@id"}, + "weight": {"@id": "schema:weight"}, + "weightTotal": {"@id": "schema:weightTotal"}, + "wheelbase": {"@id": "schema:wheelbase"}, + "width": {"@id": "schema:width"}, + "winner": {"@id": "schema:winner"}, + "wordCount": {"@id": "schema:wordCount"}, + "workExample": {"@id": "schema:workExample"}, + "workFeatured": {"@id": "schema:workFeatured"}, + "workHours": {"@id": "schema:workHours"}, + "workLocation": {"@id": "schema:workLocation"}, + "workPerformed": {"@id": "schema:workPerformed"}, + "workPresented": {"@id": "schema:workPresented"}, + "workTranslation": {"@id": "schema:workTranslation"}, + "workload": {"@id": "schema:workload"}, + "worksFor": {"@id": "schema:worksFor"}, + "worstRating": {"@id": "schema:worstRating"}, + "xpath": {"@id": "schema:xpath"}, + "yearBuilt": {"@id": "schema:yearBuilt"}, + "yearlyRevenue": {"@id": "schema:yearlyRevenue"}, + "yearsInOperation": {"@id": "schema:yearsInOperation"}, + "yield": {"@id": "schema:yield"}, + "http://publications.europa.eu/mdr/eli/index.html": { + "@id": "http://publications.europa.eu/mdr/eli/index.html" + }, + "httpMethod": {"@id": "schema:httpMethod"}, + "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#Automotive_Ontology_Working_Group": { + "@id": "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#Automotive_Ontology_Working_Group" + }, + "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#FIBO": { + "@id": "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#FIBO" + }, + "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#GLEIF": { + "@id": "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#GLEIF" + }, + "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#IIT-CNR.it": { + "@id": "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#IIT-CNR.it" + }, + "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#MBZ": { + "@id": "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#MBZ" + }, + "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#Tourism": { + "@id": "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#Tourism" + }, + "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_ActionCollabClass": { + "@id": "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_ActionCollabClass" + }, + "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_DatasetClass": { + "@id": "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_DatasetClass" + }, + "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_GoodRelationsClass": { + "@id": "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_GoodRelationsClass" + }, + "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_GoodRelationsTerms": { + "@id": "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_GoodRelationsTerms" + }, + "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_LRMIClass": { + "@id": "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_LRMIClass" + }, + "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_QAStackExchange": { + "@id": "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_QAStackExchange" + }, + "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_WikiDoc": { + "@id": "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_WikiDoc" + }, + "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_bibex": { + "@id": "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_bibex" + }, + "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_rNews": { + "@id": "http://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#source_rNews" + }, + "https://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#STI_Accommodation_Ontology": { + "@id": "https://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#STI_Accommodation_Ontology" + }, + "https://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#TP": { + "@id": "https://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#TP" + }, + "https://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#TP-draws": { + "@id": "https://www.w3.org/wiki/WebSchemas/SchemaDotOrgSources#TP-draws" + }, +} + + +CONTEXT["sc"] = "http://schema.org#" diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py index 207c4b76d..f4adbfe12 100644 --- a/api/funkwhale_api/federation/serializers.py +++ b/api/funkwhale_api/federation/serializers.py @@ -5,7 +5,8 @@ import uuid from django.core.exceptions import ObjectDoesNotExist from django.core.paginator import Paginator from django.db import transaction - +from django.urls import reverse +from django.utils import timezone from rest_framework import serializers from funkwhale_api.common import utils as common_utils @@ -23,6 +24,34 @@ from . import activity, actors, contexts, jsonld, models, tasks, utils logger = logging.getLogger(__name__) +def include_if_not_none(data, value, field): + if value is not None: + data[field] = value + + +class MultipleSerializer(serializers.Serializer): + """ + A serializer that will try multiple serializers in turn + """ + + def __init__(self, *args, **kwargs): + self.allowed = kwargs.pop("allowed") + super().__init__(*args, **kwargs) + + def to_internal_value(self, v): + last_exception = None + for serializer_class in self.allowed: + s = serializer_class(data=v) + try: + s.is_valid(raise_exception=True) + except serializers.ValidationError as e: + last_exception = e + else: + return s.validated_data + + raise last_exception + + class TruncatedCharField(serializers.CharField): def __init__(self, *args, **kwargs): self.truncate_length = kwargs.pop("truncate_length") @@ -35,6 +64,38 @@ class TruncatedCharField(serializers.CharField): return v +class TagSerializer(jsonld.JsonLdSerializer): + type = serializers.ChoiceField(choices=[contexts.AS.Hashtag]) + name = serializers.CharField(max_length=100) + + class Meta: + jsonld_mapping = {"name": jsonld.first_val(contexts.AS.name)} + + def validate_name(self, value): + if value.startswith("#"): + # remove trailing # + value = value[1:] + return value + + +def tag_list(tagged_items): + return [ + repr_tag(item.tag.name) + for item in sorted(set(tagged_items.all()), key=lambda i: i.tag.name) + ] + + +def is_mimetype(mt, allowed_mimetypes): + for allowed in allowed_mimetypes: + if allowed.endswith("/*"): + if mt.startswith(allowed.replace("*", "")): + return True + else: + if mt == allowed: + return True + return False + + class MediaSerializer(jsonld.JsonLdSerializer): mediaType = serializers.CharField() @@ -52,30 +113,51 @@ class MediaSerializer(jsonld.JsonLdSerializer): if self.allow_empty_mimetype and not v: return None - for mt in self.allowed_mimetypes: - - if mt.endswith("/*"): - if v.startswith(mt.replace("*", "")): - return v - else: - if v == mt: - return v - raise serializers.ValidationError( - "Invalid mimetype {}. Allowed: {}".format(v, self.allowed_mimetypes) - ) + if not is_mimetype(v, self.allowed_mimetypes): + raise serializers.ValidationError( + "Invalid mimetype {}. Allowed: {}".format(v, self.allowed_mimetypes) + ) + return v class LinkSerializer(MediaSerializer): type = serializers.ChoiceField(choices=[contexts.AS.Link]) href = serializers.URLField(max_length=500) + bitrate = serializers.IntegerField(min_value=0, required=False) + size = serializers.IntegerField(min_value=0, required=False) class Meta: jsonld_mapping = { "href": jsonld.first_id(contexts.AS.href), "mediaType": jsonld.first_val(contexts.AS.mediaType), + "bitrate": jsonld.first_val(contexts.FW.bitrate), + "size": jsonld.first_val(contexts.FW.size), } +class LinkListSerializer(serializers.ListField): + def __init__(self, *args, **kwargs): + kwargs.setdefault("child", LinkSerializer(jsonld_expand=False)) + self.keep_mediatype = kwargs.pop("keep_mediatype", []) + super().__init__(*args, **kwargs) + + def to_internal_value(self, v): + links = super().to_internal_value(v) + if not self.keep_mediatype: + # no further filtering required + return links + links = [ + link + for link in links + if link.get("mediaType") + and is_mimetype(link["mediaType"], self.keep_mediatype) + ] + if not self.allow_empty and len(links) == 0: + self.fail("empty") + + return links + + class ImageSerializer(MediaSerializer): type = serializers.ChoiceField(choices=[contexts.AS.Image, contexts.AS.Link]) href = serializers.URLField(max_length=500, required=False) @@ -133,6 +215,16 @@ def get_by_media_type(urls, media_type): return url +class BasicActorSerializer(jsonld.JsonLdSerializer): + id = serializers.URLField(max_length=500) + type = serializers.ChoiceField( + choices=[getattr(contexts.AS, c[0]) for c in models.TYPE_CHOICES] + ) + + class Meta: + jsonld_mapping = {} + + class ActorSerializer(jsonld.JsonLdSerializer): id = serializers.URLField(max_length=500) outbox = serializers.URLField(max_length=500, required=False) @@ -163,6 +255,16 @@ class ActorSerializer(jsonld.JsonLdSerializer): required=False, allow_empty_mimetype=True, ) + attributedTo = serializers.URLField(max_length=500, required=False) + + tags = serializers.ListField( + child=TagSerializer(), min_length=0, required=False, allow_null=True + ) + + category = serializers.CharField(required=False) + # languages = serializers.Char( + # music_models.ARTIST_CONTENT_CATEGORY_CHOICES, required=False, default="music", + # ) class Meta: # not strictly necessary because it's not a model serializer @@ -185,8 +287,19 @@ class ActorSerializer(jsonld.JsonLdSerializer): "endpoints": jsonld.first_obj(contexts.AS.endpoints), "icon": jsonld.first_obj(contexts.AS.icon), "url": jsonld.raw(contexts.AS.url), + "attributedTo": jsonld.first_id(contexts.AS.attributedTo), + "tags": jsonld.raw(contexts.AS.tag), + "category": jsonld.first_val(contexts.SC.category), + # "language": jsonld.first_val(contexts.SC.inLanguage), } + def validate_category(self, v): + return ( + v + if v in [t for t, _ in music_models.ARTIST_CONTENT_CATEGORY_CHOICES] + else None + ) + def to_representation(self, instance): ret = { "id": instance.fid, @@ -231,6 +344,9 @@ class ActorSerializer(jsonld.JsonLdSerializer): include_image(ret, channel.artist.attachment_cover, "icon") if channel.artist.description_id: ret["summary"] = channel.artist.description.rendered + ret["attributedTo"] = channel.attributed_to.fid + ret["category"] = channel.artist.content_category + ret["tag"] = tag_list(channel.artist.tagged_items.all()) else: ret["url"] = [ { @@ -312,6 +428,22 @@ class ActorSerializer(jsonld.JsonLdSerializer): if new_value else None, ) + + rss_url = get_by_media_type( + self.validated_data.get("url", []), "application/rss+xml" + ) + if rss_url: + rss_url = rss_url["href"] + attributed_to = self.validated_data.get("attributedTo") + if rss_url and attributed_to: + # if the actor is attributed to another actor, and there is a RSS url, + # then we consider it's a channel + create_or_update_channel( + actor, + rss_url=rss_url, + attributed_to_fid=attributed_to, + **self.validated_data + ) return actor def validate(self, data): @@ -326,6 +458,56 @@ class ActorSerializer(jsonld.JsonLdSerializer): return validated_data +def create_or_update_channel(actor, rss_url, attributed_to_fid, **validated_data): + from funkwhale_api.audio import models as audio_models + + attributed_to = actors.get_actor(attributed_to_fid) + artist_defaults = { + "name": validated_data.get("name", validated_data["preferredUsername"]), + "fid": validated_data["id"], + "content_category": validated_data.get("category", "music") or "music", + "attributed_to": attributed_to, + } + artist, created = music_models.Artist.objects.update_or_create( + channel__attributed_to=attributed_to, + channel__actor=actor, + defaults=artist_defaults, + ) + common_utils.attach_content(artist, "description", validated_data.get("summary")) + if "icon" in validated_data: + new_value = validated_data["icon"] + common_utils.attach_file( + artist, + "attachment_cover", + {"url": new_value["url"], "mimetype": new_value.get("mediaType")} + if new_value + else None, + ) + tags = [t["name"] for t in validated_data.get("tags", []) or []] + tags_models.set_tags(artist, *tags) + if created: + uid = uuid.uuid4() + fid = utils.full_url( + reverse("federation:music:libraries-detail", kwargs={"uuid": uid}) + ) + library = attributed_to.libraries.create( + privacy_level="everyone", name=artist_defaults["name"], fid=fid, uuid=uid, + ) + else: + library = artist.channel.library + channel_defaults = { + "actor": actor, + "attributed_to": attributed_to, + "rss_url": rss_url, + "artist": artist, + "library": library, + } + channel, created = audio_models.Channel.objects.update_or_create( + actor=actor, attributed_to=attributed_to, defaults=channel_defaults, + ) + return channel + + class APIActorSerializer(serializers.ModelSerializer): class Meta: model = models.Actor @@ -936,20 +1118,6 @@ MUSIC_ENTITY_JSONLD_MAPPING = { } -class TagSerializer(jsonld.JsonLdSerializer): - type = serializers.ChoiceField(choices=[contexts.AS.Hashtag]) - name = serializers.CharField(max_length=100) - - class Meta: - jsonld_mapping = {"name": jsonld.first_val(contexts.AS.name)} - - def validate_name(self, value): - if value.startswith("#"): - # remove trailing # - value = value[1:] - return value - - def repr_tag(tag_name): return {"type": "Hashtag", "name": "#{}".format(tag_name)} @@ -1025,10 +1193,7 @@ class MusicEntitySerializer(jsonld.JsonLdSerializer): return instance def get_tags_repr(self, instance): - return [ - repr_tag(item.tag.name) - for item in sorted(instance.tagged_items.all(), key=lambda i: i.tag.name) - ] + return tag_list(instance.tagged_items.all()) def validate_updated_data(self, instance, validated_data): try: @@ -1108,7 +1273,10 @@ class ArtistSerializer(MusicEntitySerializer): class AlbumSerializer(MusicEntitySerializer): released = serializers.DateField(allow_null=True, required=False) - artists = serializers.ListField(child=ArtistSerializer(), min_length=1) + artists = serializers.ListField( + child=MultipleSerializer(allowed=[BasicActorSerializer, ArtistSerializer]), + min_length=1, + ) # XXX: 1.0 rename to image cover = ImageSerializer( allowed_mimetypes=["image/*"], @@ -1146,16 +1314,24 @@ class AlbumSerializer(MusicEntitySerializer): "released": instance.release_date.isoformat() if instance.release_date else None, - "artists": [ - ArtistSerializer( - instance.artist, context={"include_ap_context": False} - ).data - ], "attributedTo": instance.attributed_to.fid if instance.attributed_to else None, "tag": self.get_tags_repr(instance), } + if instance.artist.get_channel(): + d["artists"] = [ + { + "type": instance.artist.channel.actor.type, + "id": instance.artist.channel.actor.fid, + } + ] + else: + d["artists"] = [ + ArtistSerializer( + instance.artist, context={"include_ap_context": False} + ).data + ] include_content(d, instance.description) if instance.attachment_cover: d["cover"] = { @@ -1172,12 +1348,18 @@ class AlbumSerializer(MusicEntitySerializer): def validate(self, data): validated_data = super().validate(data) if not self.parent: - validated_data["_artist"] = utils.retrieve_ap_object( - validated_data["artists"][0]["id"], - actor=self.context.get("fetch_actor"), - queryset=music_models.Artist, - serializer_class=ArtistSerializer, - ) + artist_data = validated_data["artists"][0] + if artist_data.get("type", "Artist") == "Artist": + validated_data["_artist"] = utils.retrieve_ap_object( + artist_data["id"], + actor=self.context.get("fetch_actor"), + queryset=music_models.Artist, + serializer_class=ArtistSerializer, + ) + else: + # we have an actor as an artist, so it's a channel + actor = actors.get_actor(artist_data["id"]) + validated_data["_artist"] = actor.channel.artist return validated_data @@ -1569,31 +1751,116 @@ class ChannelOutboxSerializer(PaginatedCollectionSerializer): return r -class ChannelUploadSerializer(serializers.Serializer): +class ChannelUploadSerializer(jsonld.JsonLdSerializer): + id = serializers.URLField(max_length=500) + type = serializers.ChoiceField(choices=[contexts.AS.Audio]) + url = LinkListSerializer(keep_mediatype=["audio/*"], min_length=1) + name = TruncatedCharField(truncate_length=music_models.MAX_LENGTHS["TRACK_TITLE"]) + published = serializers.DateTimeField(required=False) + duration = serializers.IntegerField(min_value=0, required=False) + position = serializers.IntegerField(min_value=0, allow_null=True, required=False) + disc = serializers.IntegerField(min_value=1, allow_null=True, required=False) + album = serializers.URLField(max_length=500, required=False) + license = serializers.URLField(allow_null=True, required=False) + copyright = TruncatedCharField( + truncate_length=music_models.MAX_LENGTHS["COPYRIGHT"], + allow_null=True, + required=False, + ) + image = ImageSerializer( + allowed_mimetypes=["image/*"], + allow_null=True, + required=False, + allow_empty_mimetype=True, + ) + + mediaType = serializers.ChoiceField( + choices=common_models.CONTENT_TEXT_SUPPORTED_TYPES, + default="text/html", + required=False, + ) + content = TruncatedCharField( + truncate_length=common_models.CONTENT_TEXT_MAX_LENGTH, + required=False, + allow_null=True, + ) + + tags = serializers.ListField( + child=TagSerializer(), min_length=0, required=False, allow_null=True + ) + + class Meta: + jsonld_mapping = { + "name": jsonld.first_val(contexts.AS.name), + "url": jsonld.raw(contexts.AS.url), + "published": jsonld.first_val(contexts.AS.published), + "mediaType": jsonld.first_val(contexts.AS.mediaType), + "content": jsonld.first_val(contexts.AS.content), + "duration": jsonld.first_val(contexts.AS.duration), + "album": jsonld.first_id(contexts.FW.album), + "copyright": jsonld.first_val(contexts.FW.copyright), + "disc": jsonld.first_val(contexts.FW.disc), + "license": jsonld.first_id(contexts.FW.license), + "position": jsonld.first_val(contexts.FW.position), + "image": jsonld.first_obj(contexts.AS.image), + "tags": jsonld.raw(contexts.AS.tag), + } + + def validate_album(self, v): + return utils.retrieve_ap_object( + v, + actor=actors.get_service_actor(), + serializer_class=AlbumSerializer, + queryset=music_models.Album.objects.filter( + artist__channel=self.context["channel"] + ), + ) + + def validate(self, data): + validated_data = super().validate(data) + if data.get("content"): + validated_data["description"] = { + "content_type": data["mediaType"], + "text": data["content"], + } + return validated_data + def to_representation(self, upload): data = { "id": upload.fid, "type": "Audio", - "name": upload.track.full_name, + "name": upload.track.title, "attributedTo": upload.library.channel.actor.fid, "published": upload.creation_date.isoformat(), "to": contexts.AS.Public if upload.library.privacy_level == "everyone" else "", "url": [ - { - "type": "Link", - "mediaType": upload.mimetype, - "href": utils.full_url(upload.listen_url_no_download), - }, { "type": "Link", "mediaType": "text/html", "href": utils.full_url(upload.track.get_absolute_url()), }, + { + "type": "Link", + "mediaType": upload.mimetype, + "href": utils.full_url(upload.listen_url_no_download), + }, ], } + if upload.track.album: + data["album"] = upload.track.album.fid + if upload.track.local_license: + data["license"] = upload.track.local_license["identifiers"][0] + + include_if_not_none(data, upload.duration, "duration") + include_if_not_none(data, upload.track.position, "position") + include_if_not_none(data, upload.track.disc_number, "disc") + include_if_not_none(data, upload.track.copyright, "copyright") + include_if_not_none(data["url"][1], upload.bitrate, "bitrate") + include_if_not_none(data["url"][1], upload.size, "size") include_content(data, upload.track.description) + include_image(data, upload.track.attachment_cover) tags = [item.tag.name for item in upload.get_all_tagged_items()] if tags: data["tag"] = [repr_tag(name) for name in tags] @@ -1604,6 +1871,68 @@ class ChannelUploadSerializer(serializers.Serializer): return data + def update(self, instance, validated_data): + return self.update_or_create(validated_data) + + @transaction.atomic + def update_or_create(self, validated_data): + channel = self.context["channel"] + now = timezone.now() + track_defaults = { + "fid": validated_data["id"], + "artist": channel.artist, + "position": validated_data.get("position", 1), + "disc_number": validated_data.get("disc", 1), + "title": validated_data["name"], + "copyright": validated_data.get("copyright"), + "attributed_to": channel.attributed_to, + "album": validated_data.get("album"), + "creation_date": validated_data.get("published", now), + } + if validated_data.get("license"): + track_defaults["license"] = licenses.match(validated_data["license"]) + + track, created = music_models.Track.objects.update_or_create( + artist__channel=channel, fid=validated_data["id"], defaults=track_defaults + ) + + if "image" in validated_data: + new_value = self.validated_data["image"] + common_utils.attach_file( + track, + "attachment_cover", + {"url": new_value["url"], "mimetype": new_value.get("mediaType")} + if new_value + else None, + ) + + common_utils.attach_content( + track, "description", validated_data.get("description") + ) + + tags = [t["name"] for t in validated_data.get("tags", []) or []] + tags_models.set_tags(track, *tags) + + upload_defaults = { + "fid": validated_data["id"], + "track": track, + "library": channel.library, + "creation_date": validated_data.get("published", now), + "duration": validated_data.get("duration"), + "bitrate": validated_data["url"][0].get("bitrate"), + "size": validated_data["url"][0].get("size"), + "mimetype": validated_data["url"][0]["mediaType"], + "source": validated_data["url"][0]["href"], + "import_status": "finished", + } + upload, created = music_models.Upload.objects.update_or_create( + fid=validated_data["id"], defaults=upload_defaults + ) + return upload + + def create(self, validated_data): + return self.update_or_create(validated_data) + class ChannelCreateUploadSerializer(serializers.Serializer): def to_representation(self, upload): diff --git a/api/funkwhale_api/federation/tasks.py b/api/funkwhale_api/federation/tasks.py index 04457e1fc..1a43970ef 100644 --- a/api/funkwhale_api/federation/tasks.py +++ b/api/funkwhale_api/federation/tasks.py @@ -7,11 +7,14 @@ import requests from django.conf import settings from django.db import transaction from django.db.models import Q, F +from django.db.models.deletion import Collector from django.utils import timezone from dynamic_preferences.registries import global_preferences_registry from requests.exceptions import RequestException +from funkwhale_api.audio import models as audio_models from funkwhale_api.common import preferences +from funkwhale_api.common import models as common_models from funkwhale_api.common import session from funkwhale_api.common import utils as common_utils from funkwhale_api.moderation import mrf @@ -254,8 +257,11 @@ def handle_purge_actors(ids, only=[]): # purge audio content if not only or "media" in only: + delete_qs(common_models.Attachment.objects.filter(actor__in=ids)) delete_qs(models.LibraryFollow.objects.filter(actor_id__in=ids)) delete_qs(models.Follow.objects.filter(target_id__in=ids)) + delete_qs(audio_models.Channel.objects.filter(attributed_to__in=ids)) + delete_qs(audio_models.Channel.objects.filter(actor__in=ids)) delete_qs(music_models.Upload.objects.filter(library__actor_id__in=ids)) delete_qs(music_models.Library.objects.filter(actor_id__in=ids)) @@ -390,9 +396,76 @@ def fetch(fetch_obj): error("save", message=str(e)) raise + # special case for channels + # when obj is an actor, we check if the actor has a channel associated with it + # if it is the case, we consider the fetch obj to be a channel instead + if isinstance(obj, models.Actor) and obj.get_channel(): + obj = obj.get_channel() fetch_obj.object = obj fetch_obj.status = "finished" fetch_obj.fetch_date = timezone.now() return fetch_obj.save( update_fields=["fetch_date", "status", "object_id", "object_content_type"] ) + + +class PreserveSomeDataCollector(Collector): + """ + We need to delete everything related to an actor. Well… Almost everything. + But definitely not the Delete Activity we send to announce the actor is deleted. + """ + + def __init__(self, *args, **kwargs): + self.creation_date = timezone.now() + super().__init__(*args, **kwargs) + + def related_objects(self, related, *args, **kwargs): + qs = super().related_objects(related, *args, **kwargs) + if related.name == "outbox_activities": + # exclude the delete activity can be broadcasted properly + qs = qs.exclude(type="Delete", creation_date__gte=self.creation_date) + + return qs + + +@celery.app.task(name="federation.remove_actor") +@transaction.atomic +@celery.require_instance( + models.Actor.objects.all(), "actor", +) +def remove_actor(actor): + # Then we broadcast the info over federation. We do this *before* deleting objects + # associated with the actor, otherwise follows are removed and we don't know where + # to broadcast + logger.info("Broadcasting deletion to federation…") + collector = PreserveSomeDataCollector(using="default") + routes.outbox.dispatch( + {"type": "Delete", "object": {"type": actor.type}}, context={"actor": actor} + ) + + # then we delete any object associated with the actor object, but *not* the actor + # itself. We keep it for auditability and sending the Delete ActivityPub message + logger.info( + "Prepare deletion of objects associated with account %s…", + actor.preferred_username, + ) + collector.collect([actor]) + for model, instances in collector.data.items(): + if issubclass(model, actor.__class__): + # we skip deletion of the actor itself + continue + + to_delete = model.objects.filter(pk__in=[instance.pk for instance in instances]) + logger.info( + "Deleting %s objects associated with account %s…", + len(instances), + actor.preferred_username, + ) + to_delete.delete() + + # Finally, we update the actor itself and mark it as removed + logger.info("Marking actor as Tombsone…") + actor.type = "Tombstone" + actor.name = None + actor.summary = None + actor.save(update_fields=["type", "name", "summary"]) diff --git a/api/funkwhale_api/federation/views.py b/api/funkwhale_api/federation/views.py index 7a16fbed4..cd8d0b45c 100644 --- a/api/funkwhale_api/federation/views.py +++ b/api/funkwhale_api/federation/views.py @@ -67,7 +67,11 @@ class ActorViewSet(FederationMixin, mixins.RetrieveModelMixin, viewsets.GenericV lookup_field = "preferred_username" authentication_classes = [authentication.SignatureAuthentication] renderer_classes = renderers.get_ap_renderers() - queryset = models.Actor.objects.local().select_related("user") + queryset = ( + models.Actor.objects.local() + .select_related("user", "channel__artist", "channel__attributed_to") + .prefetch_related("channel__artist__tagged_items__tag") + ) serializer_class = serializers.ActorSerializer def get_queryset(self): diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py index 3b5c33a44..80cf286af 100644 --- a/api/funkwhale_api/music/views.py +++ b/api/funkwhale_api/music/views.py @@ -241,6 +241,14 @@ class AlbumViewSet( return serializers.AlbumCreateSerializer return super().get_serializer_class() + @transaction.atomic + def perform_destroy(self, instance): + routes.outbox.dispatch( + {"type": "Delete", "object": {"type": "Album"}}, + context={"album": instance}, + ) + models.Album.objects.filter(pk=instance.pk).delete() + class LibraryViewSet( mixins.CreateModelMixin, @@ -380,6 +388,15 @@ class TrackViewSet( context["description"] = self.action in ["retrieve", "create", "update"] return context + @transaction.atomic + def perform_destroy(self, instance): + uploads = instance.uploads.order_by("id") + routes.outbox.dispatch( + {"type": "Delete", "object": {"type": "Audio"}}, + context={"uploads": list(uploads)}, + ) + instance.delete() + def strip_absolute_media_url(path): if ( diff --git a/api/funkwhale_api/users/tasks.py b/api/funkwhale_api/users/tasks.py index 40438ccff..cad4e50d1 100644 --- a/api/funkwhale_api/users/tasks.py +++ b/api/funkwhale_api/users/tasks.py @@ -1,8 +1,6 @@ import logging -from django.db.models.deletion import Collector - -from funkwhale_api.federation import routes +from funkwhale_api.federation import tasks as federation_tasks from funkwhale_api.taskapp import celery from . import models @@ -20,39 +18,6 @@ def delete_account(user): user.delete() logger.info("Deleted user object") - # Then we broadcast the info over federation. We do this *before* deleting objects - # associated with the actor, otherwise follows are removed and we don't know where - # to broadcast - logger.info("Broadcasting deletion to federation…") - routes.outbox.dispatch( - {"type": "Delete", "object": {"type": actor.type}}, context={"actor": actor} - ) - - # then we delete any object associated with the actor object, but *not* the actor - # itself. We keep it for auditability and sending the Delete ActivityPub message - collector = Collector(using="default") - logger.info( - "Prepare deletion of objects associated with account %s…", user.username - ) - collector.collect([actor]) - - for model, instances in collector.data.items(): - if issubclass(model, actor.__class__): - # we skip deletion of the actor itself - continue - - logger.info( - "Deleting %s objects associated with account %s…", - len(instances), - user.username, - ) - to_delete = model.objects.filter(pk__in=[instance.pk for instance in instances]) - to_delete.delete() - - # Finally, we update the actor itself and mark it as removed - logger.info("Marking actor as Tombsone…") - actor.type = "Tombstone" - actor.name = None - actor.summary = None - actor.save(update_fields=["type", "name", "summary"]) - logger.info("Deletion of account done %s!", user.username) + # ensure actor is set to tombstone, activities are removed, etc. + federation_tasks.remove_actor(actor_id=actor.pk) + logger.info("Deletion of account done %s!", actor.preferred_username) diff --git a/api/tests/audio/test_views.py b/api/tests/audio/test_views.py index ee037ec79..49b7eb07a 100644 --- a/api/tests/audio/test_views.py +++ b/api/tests/audio/test_views.py @@ -148,19 +148,17 @@ def test_channel_delete(logged_in_api_client, factories, mocker): channel = factories["audio.Channel"](attributed_to=actor) url = reverse("api:v1:channels-detail", kwargs={"composite": channel.uuid}) - dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch") + on_commit = mocker.patch("funkwhale_api.common.utils.on_commit") response = logged_in_api_client.delete(url) assert response.status_code == 204 + on_commit.assert_called_once_with( + views.federation_tasks.remove_actor.delay, actor_id=channel.actor.pk + ) with pytest.raises(channel.DoesNotExist): channel.refresh_from_db() - dispatch.assert_called_once_with( - {"type": "Delete", "object": {"type": channel.actor.type}}, - context={"actor": channel.actor}, - ) - def test_channel_delete_permission(logged_in_api_client, factories): logged_in_api_client.user.create_actor() @@ -218,6 +216,38 @@ def test_channel_unsubscribe(factories, logged_in_api_client): subscription.refresh_from_db() +def test_channel_subscribe_remote(factories, logged_in_api_client, mocker): + dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch") + actor = logged_in_api_client.user.create_actor() + channel_actor = factories["federation.Actor"]() + channel = factories["audio.Channel"](artist__description=None, actor=channel_actor) + url = reverse("api:v1:channels-subscribe", kwargs={"composite": channel.uuid}) + + response = logged_in_api_client.post(url) + + assert response.status_code == 201 + subscription = actor.emitted_follows.latest("id") + dispatch.assert_called_once_with( + {"type": "Follow"}, context={"follow": subscription} + ) + + +def test_channel_unsubscribe_remote(factories, logged_in_api_client, mocker): + dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch") + actor = logged_in_api_client.user.create_actor() + channel_actor = factories["federation.Actor"]() + channel = factories["audio.Channel"](actor=channel_actor) + subscription = factories["audio.Subscription"](target=channel.actor, actor=actor) + url = reverse("api:v1:channels-unsubscribe", kwargs={"composite": channel.uuid}) + + response = logged_in_api_client.post(url) + + assert response.status_code == 204 + dispatch.assert_called_once_with( + {"type": "Undo", "object": {"type": "Follow"}}, context={"follow": subscription} + ) + + def test_subscriptions_list(factories, logged_in_api_client): actor = logged_in_api_client.user.create_actor() channel = factories["audio.Channel"]( diff --git a/api/tests/federation/test_api_serializers.py b/api/tests/federation/test_api_serializers.py index b914bc67f..beea844b6 100644 --- a/api/tests/federation/test_api_serializers.py +++ b/api/tests/federation/test_api_serializers.py @@ -167,6 +167,7 @@ def test_fetch_serializer_no_obj(factories, to_api_date): ("music.Track", "track", "id"), ("music.Library", "library", "uuid"), ("music.Upload", "upload", "uuid"), + ("audio.Channel", "channel", "uuid"), ("federation.Actor", "account", "full_username"), ], ) diff --git a/api/tests/federation/test_routes.py b/api/tests/federation/test_routes.py index 1ce6499ec..0ae18bd15 100644 --- a/api/tests/federation/test_routes.py +++ b/api/tests/federation/test_routes.py @@ -26,6 +26,7 @@ from funkwhale_api.moderation import serializers as moderation_serializers routes.inbox_delete_library, ), ({"type": "Delete", "object": {"type": "Audio"}}, routes.inbox_delete_audio), + ({"type": "Delete", "object": {"type": "Album"}}, routes.inbox_delete_album), ({"type": "Undo", "object": {"type": "Follow"}}, routes.inbox_undo_follow), ({"type": "Update", "object": {"type": "Artist"}}, routes.inbox_update_artist), ({"type": "Update", "object": {"type": "Album"}}, routes.inbox_update_album), @@ -58,6 +59,7 @@ def test_inbox_routes(route, handler): routes.outbox_delete_library, ), ({"type": "Delete", "object": {"type": "Audio"}}, routes.outbox_delete_audio), + ({"type": "Delete", "object": {"type": "Album"}}, routes.outbox_delete_album), ({"type": "Undo", "object": {"type": "Follow"}}, routes.outbox_undo_follow), ({"type": "Update", "object": {"type": "Track"}}, routes.outbox_update_track), ( @@ -349,6 +351,34 @@ def test_inbox_create_audio(factories, mocker): assert save.call_count == 1 +def test_inbox_create_audio_channel(factories, mocker): + activity = factories["federation.Activity"]() + channel = factories["audio.Channel"]() + album = factories["music.Album"](artist=channel.artist) + upload = factories["music.Upload"](track__album=album, library=channel.library,) + payload = { + "@context": jsonld.get_default_context(), + "type": "Create", + "actor": channel.actor.fid, + "object": serializers.ChannelUploadSerializer(upload).data, + } + upload.delete() + init = mocker.spy(serializers.ChannelUploadSerializer, "__init__") + save = mocker.spy(serializers.ChannelUploadSerializer, "save") + result = routes.inbox_create_audio( + payload, + context={"actor": channel.actor, "raise_exception": True, "activity": activity}, + ) + assert channel.library.uploads.count() == 1 + assert result == {"object": channel.library.uploads.latest("id"), "target": channel} + + assert init.call_count == 1 + args = init.call_args + assert args[1]["data"] == payload["object"] + assert args[1]["context"] == {"channel": channel} + assert save.call_count == 1 + + def test_inbox_delete_library(factories): activity = factories["federation.Activity"]() @@ -368,6 +398,73 @@ def test_inbox_delete_library(factories): library.refresh_from_db() +def test_inbox_delete_album(factories): + + album = factories["music.Album"](attributed=True) + payload = { + "type": "Delete", + "actor": album.attributed_to.fid, + "object": {"type": "Album", "id": album.fid}, + } + + routes.inbox_delete_album( + payload, + context={ + "actor": album.attributed_to, + "raise_exception": True, + "activity": activity, + }, + ) + + with pytest.raises(album.__class__.DoesNotExist): + album.refresh_from_db() + + +def test_inbox_delete_album_channel(factories): + channel = factories["audio.Channel"]() + album = factories["music.Album"](artist=channel.artist) + payload = { + "type": "Delete", + "actor": channel.actor.fid, + "object": {"type": "Album", "id": album.fid}, + } + + routes.inbox_delete_album( + payload, + context={"actor": channel.actor, "raise_exception": True, "activity": activity}, + ) + + with pytest.raises(album.__class__.DoesNotExist): + album.refresh_from_db() + + +def test_outbox_delete_album(factories): + album = factories["music.Album"](attributed=True) + a = list(routes.outbox_delete_album({"album": album}))[0] + expected = serializers.ActivitySerializer( + {"type": "Delete", "object": {"type": "Album", "id": album.fid}} + ).data + + expected["to"] = [activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}] + + assert dict(a["payload"]) == dict(expected) + assert a["actor"] == album.attributed_to + + +def test_outbox_delete_album_channel(factories): + channel = factories["audio.Channel"]() + album = factories["music.Album"](artist=channel.artist) + a = list(routes.outbox_delete_album({"album": album}))[0] + expected = serializers.ActivitySerializer( + {"type": "Delete", "object": {"type": "Album", "id": album.fid}} + ).data + + expected["to"] = [activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}] + + assert dict(a["payload"]) == dict(expected) + assert a["actor"] == channel.actor + + def test_inbox_delete_library_impostor(factories): activity = factories["federation.Activity"]() impostor = factories["federation.Actor"]() @@ -469,6 +566,25 @@ def test_inbox_delete_audio(factories): upload.refresh_from_db() +def test_inbox_delete_audio_channel(factories): + activity = factories["federation.Activity"]() + channel = factories["audio.Channel"]() + upload = factories["music.Upload"](track__artist=channel.artist) + payload = { + "type": "Delete", + "actor": channel.actor.fid, + "object": {"type": "Audio", "id": [upload.fid]}, + } + + routes.inbox_delete_audio( + payload, + context={"actor": channel.actor, "raise_exception": True, "activity": activity}, + ) + + with pytest.raises(upload.__class__.DoesNotExist): + upload.refresh_from_db() + + def test_inbox_delete_audio_impostor(factories): activity = factories["federation.Activity"]() impostor = factories["federation.Actor"]() diff --git a/api/tests/federation/test_serializers.py b/api/tests/federation/test_serializers.py index c77f9940d..0c7166728 100644 --- a/api/tests/federation/test_serializers.py +++ b/api/tests/federation/test_serializers.py @@ -795,6 +795,17 @@ def test_activity_pub_album_serializer_to_ap(factories): assert serializer.data == expected +def test_activity_pub_album_serializer_to_ap_channel_artist(factories): + channel = factories["audio.Channel"]() + album = factories["music.Album"](artist=channel.artist,) + + serializer = serializers.AlbumSerializer(album) + + assert serializer.data["artists"] == [ + {"type": channel.actor.type, "id": channel.actor.fid} + ] + + def test_activity_pub_album_serializer_from_ap_create(factories, faker, now): actor = factories["federation.Actor"]() artist = factories["music.Artist"]() @@ -836,6 +847,30 @@ def test_activity_pub_album_serializer_from_ap_create(factories, faker, now): ] +def test_activity_pub_album_serializer_from_ap_create_channel_artist( + factories, faker, now +): + actor = factories["federation.Actor"]() + channel = factories["audio.Channel"]() + released = faker.date_object() + payload = { + "@context": jsonld.get_default_context(), + "type": "Album", + "id": "https://album.example", + "name": faker.sentence(), + "published": now.isoformat(), + "released": released.isoformat(), + "artists": [{"type": channel.actor.type, "id": channel.actor.fid}], + "attributedTo": actor.fid, + } + serializer = serializers.AlbumSerializer(data=payload) + assert serializer.is_valid(raise_exception=True) is True + + album = serializer.save() + + assert album.artist == channel.artist + + def test_activity_pub_album_serializer_from_ap_update(factories, faker): album = factories["music.Album"](attributed=True) released = faker.date_object() @@ -1395,7 +1430,9 @@ def test_track_serializer_update_license(factories): def test_channel_actor_serializer(factories): channel = factories["audio.Channel"]( - actor__attachment_icon=None, artist__with_cover=True + actor__attachment_icon=None, + artist__with_cover=True, + artist__set_tags=["punk", "rock"], ) serializer = serializers.ActorSerializer(channel.actor) @@ -1418,6 +1455,164 @@ def test_channel_actor_serializer(factories): } assert serializer.data["url"] == expected_url assert serializer.data["icon"] == expected_icon + assert serializer.data["attributedTo"] == channel.attributed_to.fid + assert serializer.data["category"] == channel.artist.content_category + assert serializer.data["tag"] == [ + {"type": "Hashtag", "name": "#punk"}, + {"type": "Hashtag", "name": "#rock"}, + ] + + +def test_channel_actor_serializer_from_ap_create(mocker, factories): + domain = factories["federation.Domain"](name="test.pod") + attributed_to = factories["federation.Actor"](domain=domain) + get_actor = mocker.patch.object(actors, "get_actor", return_value=attributed_to) + actor_data = { + "@context": jsonld.get_default_context(), + "followers": "https://test.pod/federation/actors/mychannel/followers", + "preferredUsername": "mychannel", + "id": "https://test.pod/federation/actors/mychannel", + "endpoints": {"sharedInbox": "https://test.pod/federation/shared/inbox"}, + "name": "mychannel", + "following": "https://test.pod/federation/actors/mychannel/following", + "outbox": "https://test.pod/federation/actors/mychannel/outbox", + "url": [ + { + "mediaType": "text/html", + "href": "https://test.pod/channels/mychannel", + "type": "Link", + }, + { + "mediaType": "application/rss+xml", + "href": "https://test.pod/api/v1/channels/mychannel/rss", + "type": "Link", + }, + ], + "type": "Person", + "category": "podcast", + "attributedTo": attributed_to.fid, + "manuallyApprovesFollowers": False, + "inbox": "https://test.pod/federation/actors/mychannel/inbox", + "icon": { + "mediaType": "image/jpeg", + "type": "Image", + "url": "https://test.pod/media/attachments/dd/ce/b2/nosmile.jpeg", + }, + "summary": "
content
", + "publicKey": { + "owner": "https://test.pod/federation/actors/mychannel", + "publicKeyPem": "-----BEGIN RSA PUBLIC KEY-----\n+KwIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "id": "https://test.pod/federation/actors/mychannel#main-key", + }, + "tag": [ + {"type": "Hashtag", "name": "#Indie"}, + {"type": "Hashtag", "name": "#Punk"}, + {"type": "Hashtag", "name": "#Rock"}, + ], + } + + serializer = serializers.ActorSerializer(data=actor_data) + assert serializer.is_valid(raise_exception=True) is True + actor = serializer.save() + + get_actor.assert_called_once_with(actor_data["attributedTo"]) + assert actor.preferred_username == actor_data["preferredUsername"] + assert actor.fid == actor_data["id"] + assert actor.name == actor_data["name"] + assert actor.type == actor_data["type"] + assert actor.public_key == actor_data["publicKey"]["publicKeyPem"] + assert actor.outbox_url == actor_data["outbox"] + assert actor.inbox_url == actor_data["inbox"] + assert actor.shared_inbox_url == actor_data["endpoints"]["sharedInbox"] + assert actor.channel.attributed_to == attributed_to + assert actor.channel.rss_url == actor_data["url"][1]["href"] + assert actor.channel.artist.attributed_to == attributed_to + assert actor.channel.artist.content_category == actor_data["category"] + assert actor.channel.artist.name == actor_data["name"] + assert actor.channel.artist.get_tags() == ["Indie", "Punk", "Rock"] + assert actor.channel.artist.description.text == actor_data["summary"] + assert actor.channel.artist.description.content_type == "text/html" + assert actor.channel.artist.attachment_cover.url == actor_data["icon"]["url"] + assert ( + actor.channel.artist.attachment_cover.mimetype + == actor_data["icon"]["mediaType"] + ) + assert actor.channel.library.fid is not None + assert actor.channel.library.actor == attributed_to + assert actor.channel.library.privacy_level == "everyone" + assert actor.channel.library.name == actor_data["name"] + + +def test_channel_actor_serializer_from_ap_update(mocker, factories): + domain = factories["federation.Domain"](name="test.pod") + attributed_to = factories["federation.Actor"](domain=domain) + actor = factories["federation.Actor"](domain=domain) + channel = factories["audio.Channel"](actor=actor, attributed_to=attributed_to) + get_actor = mocker.patch.object(actors, "get_actor", return_value=attributed_to) + library = channel.library + actor_data = { + "@context": jsonld.get_default_context(), + "followers": "https://test.pod/federation/actors/mychannel/followers", + "preferredUsername": "mychannel", + "id": actor.fid, + "endpoints": {"sharedInbox": "https://test.pod/federation/shared/inbox"}, + "name": "mychannel", + "following": "https://test.pod/federation/actors/mychannel/following", + "outbox": "https://test.pod/federation/actors/mychannel/outbox", + "url": [ + { + "mediaType": "text/html", + "href": "https://test.pod/channels/mychannel", + "type": "Link", + }, + { + "mediaType": "application/rss+xml", + "href": "https://test.pod/api/v1/channels/mychannel/rss", + "type": "Link", + }, + ], + "type": "Person", + "category": "podcast", + "attributedTo": attributed_to.fid, + "manuallyApprovesFollowers": False, + "inbox": "https://test.pod/federation/actors/mychannel/inbox", + "icon": { + "mediaType": "image/jpeg", + "type": "Image", + "url": "https://test.pod/media/attachments/dd/ce/b2/nosmile.jpeg", + }, + "summary": "content
", + "publicKey": { + "owner": "https://test.pod/federation/actors/mychannel", + "publicKeyPem": "-----BEGIN RSA PUBLIC KEY-----\n+KwIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "id": "https://test.pod/federation/actors/mychannel#main-key", + }, + "tag": [ + {"type": "Hashtag", "name": "#Indie"}, + {"type": "Hashtag", "name": "#Punk"}, + {"type": "Hashtag", "name": "#Rock"}, + ], + } + + serializer = serializers.ActorSerializer(data=actor_data) + assert serializer.is_valid(raise_exception=True) is True + serializer.save() + channel.refresh_from_db() + get_actor.assert_called_once_with(actor_data["attributedTo"]) + assert channel.actor == actor + assert channel.attributed_to == attributed_to + assert channel.rss_url == actor_data["url"][1]["href"] + assert channel.artist.attributed_to == attributed_to + assert channel.artist.content_category == actor_data["category"] + assert channel.artist.name == actor_data["name"] + assert channel.artist.get_tags() == ["Indie", "Punk", "Rock"] + assert channel.artist.description.text == actor_data["summary"] + assert channel.artist.description.content_type == "text/html" + assert channel.artist.attachment_cover.url == actor_data["icon"]["url"] + assert channel.artist.attachment_cover.mimetype == actor_data["icon"]["mediaType"] + assert channel.library.actor == attributed_to + assert channel.library.privacy_level == library.privacy_level + assert channel.library.name == library.name def test_channel_actor_outbox_serializer(factories): @@ -1449,12 +1644,21 @@ def test_channel_actor_outbox_serializer(factories): def test_channel_upload_serializer(factories): channel = factories["audio.Channel"](library__privacy_level="everyone") content = factories["common.Content"]() + cover = factories["common.Attachment"]() upload = factories["music.Upload"]( playable=True, + bitrate=543, + size=543, + duration=54, library=channel.library, import_status="finished", track__set_tags=["Punk"], + track__attachment_cover=cover, track__description=content, + track__disc_number=3, + track__position=12, + track__license="cc0-1.0", + track__copyright="Copyright something", track__album__set_tags=["Rock"], track__artist__set_tags=["Indie"], ) @@ -1463,25 +1667,38 @@ def test_channel_upload_serializer(factories): "@context": jsonld.get_default_context(), "type": "Audio", "id": upload.fid, - "name": upload.track.full_name, + "name": upload.track.title, "summary": "#Indie #Punk #Rock", "attributedTo": channel.actor.fid, "published": upload.creation_date.isoformat(), "mediaType": "text/html", "content": common_utils.render_html(content.text, content.content_type), "to": "https://www.w3.org/ns/activitystreams#Public", + "position": upload.track.position, + "duration": upload.duration, + "album": upload.track.album.fid, + "disc": upload.track.disc_number, + "copyright": upload.track.copyright, + "license": upload.track.local_license["identifiers"][0], "url": [ - { - "type": "Link", - "mediaType": upload.mimetype, - "href": utils.full_url(upload.listen_url_no_download), - }, { "type": "Link", "mediaType": "text/html", "href": utils.full_url(upload.track.get_absolute_url()), }, + { + "type": "Link", + "mediaType": upload.mimetype, + "href": utils.full_url(upload.listen_url_no_download), + "bitrate": upload.bitrate, + "size": upload.size, + }, ], + "image": { + "type": "Image", + "url": upload.track.attachment_cover.download_url_original, + "mediaType": upload.track.attachment_cover.mimetype, + }, "tag": [ {"type": "Hashtag", "name": "#Indie"}, {"type": "Hashtag", "name": "#Punk"}, @@ -1494,6 +1711,166 @@ def test_channel_upload_serializer(factories): assert serializer.data == expected +def test_channel_upload_serializer_from_ap_create(factories, now): + channel = factories["audio.Channel"](library__privacy_level="everyone") + album = factories["music.Album"](artist=channel.artist) + payload = { + "@context": jsonld.get_default_context(), + "type": "Audio", + "id": "https://test.pod/uuid", + "name": "My test track", + "summary": "#Indie #Punk #Rock", + "attributedTo": channel.actor.fid, + "published": now.isoformat(), + "mediaType": "text/html", + "content": "Hello
", + "duration": 543, + "position": 4, + "disc": 2, + "album": album.fid, + "to": "https://www.w3.org/ns/activitystreams#Public", + "copyright": "Copyright test", + "license": "http://creativecommons.org/publicdomain/zero/1.0/", + "url": [ + { + "type": "Link", + "mediaType": "text/html", + "href": "https://test.pod/track", + }, + { + "type": "Link", + "mediaType": "audio/mpeg", + "href": "https://test.pod/file.mp3", + "bitrate": 192000, + "size": 15492738, + }, + ], + "tag": [ + {"type": "Hashtag", "name": "#Indie"}, + {"type": "Hashtag", "name": "#Punk"}, + {"type": "Hashtag", "name": "#Rock"}, + ], + "image": { + "type": "Image", + "mediaType": "image/jpeg", + "url": "https://image.example/image.png", + }, + } + + serializer = serializers.ChannelUploadSerializer( + data=payload, context={"channel": channel} + ) + assert serializer.is_valid(raise_exception=True) is True + + upload = serializer.save(channel=channel) + + assert upload.library == channel.library + assert upload.import_status == "finished" + assert upload.creation_date == now + assert upload.fid == payload["id"] + assert upload.source == payload["url"][1]["href"] + assert upload.mimetype == payload["url"][1]["mediaType"] + assert upload.size == payload["url"][1]["size"] + assert upload.bitrate == payload["url"][1]["bitrate"] + assert upload.duration == payload["duration"] + assert upload.track.artist == channel.artist + assert upload.track.position == payload["position"] + assert upload.track.disc_number == payload["disc"] + assert upload.track.attributed_to == channel.attributed_to + assert upload.track.title == payload["name"] + assert upload.track.creation_date == now + assert upload.track.description.content_type == payload["mediaType"] + assert upload.track.description.text == payload["content"] + assert upload.track.fid == payload["id"] + assert upload.track.license.pk == "cc0-1.0" + assert upload.track.copyright == payload["copyright"] + assert upload.track.get_tags() == ["Indie", "Punk", "Rock"] + assert upload.track.attachment_cover.mimetype == payload["image"]["mediaType"] + assert upload.track.attachment_cover.url == payload["image"]["url"] + assert upload.track.album == album + + +def test_channel_upload_serializer_from_ap_update(factories, now): + channel = factories["audio.Channel"](library__privacy_level="everyone") + album = factories["music.Album"](artist=channel.artist) + upload = factories["music.Upload"](track__album=album, track__artist=channel.artist) + + payload = { + "@context": jsonld.get_default_context(), + "type": "Audio", + "id": upload.fid, + "name": "Hello there", + "attributedTo": channel.actor.fid, + "published": now.isoformat(), + "mediaType": "text/html", + "content": "Hello
", + "duration": 543, + "position": 4, + "disc": 2, + "album": album.fid, + "to": "https://www.w3.org/ns/activitystreams#Public", + "copyright": "Copyright test", + "license": "http://creativecommons.org/publicdomain/zero/1.0/", + "url": [ + { + "type": "Link", + "mediaType": "text/html", + "href": "https://test.pod/track", + }, + { + "type": "Link", + "mediaType": "audio/mpeg", + "href": "https://test.pod/file.mp3", + "bitrate": 192000, + "size": 15492738, + }, + ], + "tag": [ + {"type": "Hashtag", "name": "#Indie"}, + {"type": "Hashtag", "name": "#Punk"}, + {"type": "Hashtag", "name": "#Rock"}, + ], + "image": { + "type": "Image", + "mediaType": "image/jpeg", + "url": "https://image.example/image.png", + }, + } + + serializer = serializers.ChannelUploadSerializer( + data=payload, context={"channel": channel} + ) + assert serializer.is_valid(raise_exception=True) is True + + serializer.save(channel=channel) + upload.refresh_from_db() + + assert upload.library == channel.library + assert upload.import_status == "finished" + assert upload.creation_date == now + assert upload.fid == payload["id"] + assert upload.source == payload["url"][1]["href"] + assert upload.mimetype == payload["url"][1]["mediaType"] + assert upload.size == payload["url"][1]["size"] + assert upload.bitrate == payload["url"][1]["bitrate"] + assert upload.duration == payload["duration"] + assert upload.track.artist == channel.artist + assert upload.track.position == payload["position"] + assert upload.track.disc_number == payload["disc"] + assert upload.track.attributed_to == channel.attributed_to + assert upload.track.title == payload["name"] + assert upload.track.creation_date == now + assert upload.track.description.content_type == payload["mediaType"] + assert upload.track.description.text == payload["content"] + assert upload.track.fid == payload["id"] + assert upload.track.license.pk == "cc0-1.0" + assert upload.track.copyright == payload["copyright"] + assert upload.track.get_tags() == ["Indie", "Punk", "Rock"] + assert upload.track.attachment_cover.mimetype == payload["image"]["mediaType"] + assert upload.track.attachment_cover.url == payload["image"]["url"] + assert upload.track.album == album + + def test_channel_create_upload_serializer(factories): channel = factories["audio.Channel"]() upload = factories["music.Upload"]( diff --git a/api/tests/federation/test_tasks.py b/api/tests/federation/test_tasks.py index f6aef6eba..433435efb 100644 --- a/api/tests/federation/test_tasks.py +++ b/api/tests/federation/test_tasks.py @@ -491,6 +491,21 @@ def test_fetch_url(factory_name, serializer_class, factories, r_mock, mocker): assert save.call_count == 1 +def test_fetch_channel_actor_returns_channel(factories, r_mock): + obj = factories["audio.Channel"]() + fetch = factories["federation.Fetch"](url=obj.actor.fid) + payload = serializers.ActorSerializer(obj.actor).data + + r_mock.get(obj.fid, json=payload) + + tasks.fetch(fetch_id=fetch.pk) + + fetch.refresh_from_db() + + assert fetch.status == "finished" + assert fetch.object == obj + + def test_fetch_honor_instance_policy_domain(factories): domain = factories["moderation.InstancePolicy"]( block_all=True, for_domain=True diff --git a/api/tests/music/test_views.py b/api/tests/music/test_views.py index b0bbc164f..db39f571f 100644 --- a/api/tests/music/test_views.py +++ b/api/tests/music/test_views.py @@ -1407,7 +1407,8 @@ def test_channel_owner_can_create_album(factories, logged_in_api_client): assert album.description.text == "hello world" -def test_channel_owner_can_delete_album(factories, logged_in_api_client): +def test_channel_owner_can_delete_album(factories, logged_in_api_client, mocker): + dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch") actor = logged_in_api_client.user.create_actor() channel = factories["audio.Channel"](attributed_to=actor) album = factories["music.Album"](artist=channel.artist) @@ -1416,6 +1417,10 @@ def test_channel_owner_can_delete_album(factories, logged_in_api_client): response = logged_in_api_client.delete(url) assert response.status_code == 204 + + dispatch.assert_called_once_with( + {"type": "Delete", "object": {"type": "Album"}}, context={"album": album} + ) with pytest.raises(album.DoesNotExist): album.refresh_from_db() @@ -1452,15 +1457,22 @@ def test_other_user_cannot_delete_album(factories, logged_in_api_client): album.refresh_from_db() -def test_channel_owner_can_delete_track(factories, logged_in_api_client): +def test_channel_owner_can_delete_track(factories, logged_in_api_client, mocker): + dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch") actor = logged_in_api_client.user.create_actor() channel = factories["audio.Channel"](attributed_to=actor) track = factories["music.Track"](artist=channel.artist) + upload1 = factories["music.Upload"](track=track) + upload2 = factories["music.Upload"](track=track) url = reverse("api:v1:tracks-detail", kwargs={"pk": track.pk}) response = logged_in_api_client.delete(url) assert response.status_code == 204 + dispatch.assert_called_once_with( + {"type": "Delete", "object": {"type": "Audio"}}, + context={"uploads": [upload1, upload2]}, + ) with pytest.raises(track.DoesNotExist): track.refresh_from_db() diff --git a/api/tests/users/test_tasks.py b/api/tests/users/test_tasks.py index 243071850..256c21cbc 100644 --- a/api/tests/users/test_tasks.py +++ b/api/tests/users/test_tasks.py @@ -7,9 +7,10 @@ from funkwhale_api.users import tasks def test_delete_account(factories, mocker): user = factories["users.User"]() actor = user.create_actor() + factories["federation.Follow"](target=actor, approved=True) library = factories["music.Library"](actor=actor) unrelated_library = factories["music.Library"]() - dispatch = mocker.patch.object(routes.outbox, "dispatch") + dispatch = mocker.spy(routes.outbox, "dispatch") tasks.delete_account(user_id=user.pk) @@ -30,3 +31,5 @@ def test_delete_account(factories, mocker): assert actor.type == "Tombstone" assert actor.name is None assert actor.summary is None + # this activity shouldn't be deleted + assert actor.outbox_activities.filter(type="Delete").count() == 1 diff --git a/front/src/components/RemoteSearchForm.vue b/front/src/components/RemoteSearchForm.vue index 56167216f..6235e15f8 100644 --- a/front/src/components/RemoteSearchForm.vue +++ b/front/src/components/RemoteSearchForm.vue @@ -99,6 +99,8 @@ export default { return {name: 'library.tracks.detail', params: {id: this.objInfo.id}} case 'upload': return {name: 'library.uploads.detail', params: {id: this.objInfo.uuid}} + case 'channel': + return {name: 'channels.detail', params: {id: this.objInfo.uuid}} default: break; @@ -147,7 +149,6 @@ export default { return } if (this.standalone) { - console.log('HELLO') this.$router.replace({name: "search", query: {id: this.id, type: 'rss'}}) } this.fetch = null diff --git a/front/src/views/channels/DetailBase.vue b/front/src/views/channels/DetailBase.vue index 9250cc588..0fa345a2d 100644 --- a/front/src/views/channels/DetailBase.vue +++ b/front/src/views/channels/DetailBase.vue @@ -23,9 +23,9 @@ :translate-params="{count: totalTracks}"> %{ count } episode - - ·