From f2e947f05fdd2d37bb04f2bbd2c95abf176e9dc4 Mon Sep 17 00:00:00 2001 From: jo Date: Tue, 28 Jun 2022 10:21:36 +0200 Subject: [PATCH] chore(api): rename file model fields - Removed checksum field - Removed ftype field - Removed silan_check field --- .../schedule/tests/views/test_schedule.py | 30 +- api/libretime_api/storage/models/file.py | 155 +++-- api/schema.yml | 550 +++++++++--------- 3 files changed, 375 insertions(+), 360 deletions(-) diff --git a/api/libretime_api/schedule/tests/views/test_schedule.py b/api/libretime_api/schedule/tests/views/test_schedule.py index 57cc19fa4..559b47d6c 100644 --- a/api/libretime_api/schedule/tests/views/test_schedule.py +++ b/api/libretime_api/schedule/tests/views/test_schedule.py @@ -20,8 +20,8 @@ class TestScheduleViewSet(APITestCase): mime="audio/mp3", filepath=AUDIO_FILENAME, length=timedelta(seconds=40.86), - cuein=timedelta(seconds=0), - cueout=timedelta(seconds=40.8131), + cue_in=timedelta(seconds=0), + cue_out=timedelta(seconds=40.8131), ) show = baker.make( "schedule.ShowInstance", @@ -32,7 +32,7 @@ class TestScheduleViewSet(APITestCase): "schedule.Schedule", starts=datetime.now(tz=timezone.utc), ends=datetime.now(tz=timezone.utc) + file.length, - cue_out=file.cueout, + cue_out=file.cue_out, instance=show, file=file, ) @@ -43,7 +43,7 @@ class TestScheduleViewSet(APITestCase): self.assertEqual( dateparse.parse_datetime(result[0]["ends"]), schedule_item.ends ) - self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), file.cueout) + self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), file.cue_out) def test_schedule_item_trunc(self): file = baker.make( @@ -51,8 +51,8 @@ class TestScheduleViewSet(APITestCase): mime="audio/mp3", filepath=AUDIO_FILENAME, length=timedelta(seconds=40.86), - cuein=timedelta(seconds=0), - cueout=timedelta(seconds=40.8131), + cue_in=timedelta(seconds=0), + cue_out=timedelta(seconds=40.8131), ) show = baker.make( "schedule.ShowInstance", @@ -83,8 +83,8 @@ class TestScheduleViewSet(APITestCase): mime="audio/mp3", filepath=AUDIO_FILENAME, length=timedelta(seconds=40.86), - cuein=timedelta(seconds=0), - cueout=timedelta(seconds=40.8131), + cue_in=timedelta(seconds=0), + cue_out=timedelta(seconds=40.8131), ) show = baker.make( "schedule.ShowInstance", @@ -95,7 +95,7 @@ class TestScheduleViewSet(APITestCase): "schedule.Schedule", starts=datetime.now(tz=timezone.utc), ends=datetime.now(tz=timezone.utc) + file.length, - cue_out=file.cueout, + cue_out=file.cue_out, instance=show, file=file, ) @@ -103,7 +103,7 @@ class TestScheduleViewSet(APITestCase): "schedule.Schedule", starts=show.ends + timedelta(minutes=1), ends=show.ends + timedelta(minutes=1) + file.length, - cue_out=file.cueout, + cue_out=file.cue_out, instance=show, file=file, ) @@ -116,7 +116,7 @@ class TestScheduleViewSet(APITestCase): self.assertEqual( dateparse.parse_datetime(result[0]["ends"]), schedule_item.ends ) - self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), file.cueout) + self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), file.cue_out) def test_schedule_item_range(self): file = baker.make( @@ -124,8 +124,8 @@ class TestScheduleViewSet(APITestCase): mime="audio/mp3", filepath=AUDIO_FILENAME, length=timedelta(seconds=40.86), - cuein=timedelta(seconds=0), - cueout=timedelta(seconds=40.8131), + cue_in=timedelta(seconds=0), + cue_out=timedelta(seconds=40.8131), ) filter_point = datetime.now(tz=timezone.utc) @@ -138,7 +138,7 @@ class TestScheduleViewSet(APITestCase): "schedule.Schedule", starts=filter_point, ends=filter_point + file.length, - cue_out=file.cueout, + cue_out=file.cue_out, instance=show, file=file, ) @@ -146,7 +146,7 @@ class TestScheduleViewSet(APITestCase): "schedule.Schedule", starts=filter_point - timedelta(minutes=5), ends=filter_point - timedelta(minutes=5) + file.length, - cue_out=file.cueout, + cue_out=file.cue_out, instance=show, file=file, ) diff --git a/api/libretime_api/storage/models/file.py b/api/libretime_api/storage/models/file.py index 05b83b19f..4d5d39d2a 100644 --- a/api/libretime_api/storage/models/file.py +++ b/api/libretime_api/storage/models/file.py @@ -9,12 +9,38 @@ class File(models.Model): db_column="track_type", ) - name = models.CharField(max_length=255) - mime = models.CharField(max_length=255) - ftype = models.CharField(max_length=128) + owner = models.ForeignKey( + "core.User", + models.DO_NOTHING, + blank=True, + null=True, + ) + + class ImportStatus(models.IntegerChoices): + SUCCESS = 0, "Success" + PENDING = 1, "Pending" + FAILED = 2, "Failed" + + import_status = models.IntegerField( + choices=ImportStatus.choices, + default=ImportStatus.PENDING, + ) + filepath = models.TextField(blank=True, null=True) - import_status = models.IntegerField() - currently_accessing = models.IntegerField(db_column="currentlyaccessing") + size = models.IntegerField(db_column="filesize") + exists = models.BooleanField(blank=True, null=True, db_column="file_exists") + mime = models.CharField(max_length=255) + md5 = models.CharField(max_length=32, blank=True, null=True) + + hidden = models.BooleanField(blank=True, null=True) + accessed = models.IntegerField(db_column="currentlyaccessing") + scheduled = models.BooleanField(blank=True, null=True, db_column="is_scheduled") + part_of_list = models.BooleanField(blank=True, null=True, db_column="is_playlist") + + created_at = models.DateTimeField(blank=True, null=True, db_column="utime") + updated_at = models.DateTimeField(blank=True, null=True, db_column="mtime") + last_played_at = models.DateTimeField(blank=True, null=True, db_column="lptime") + edited_by = models.ForeignKey( "core.User", on_delete=models.DO_NOTHING, @@ -23,73 +49,76 @@ class File(models.Model): related_name="edited_files", db_column="editedby", ) - mtime = models.DateTimeField(blank=True, null=True) - utime = models.DateTimeField(blank=True, null=True) - lptime = models.DateTimeField(blank=True, null=True) - md5 = models.CharField(max_length=32, blank=True, null=True) - track_title = models.CharField(max_length=512, blank=True, null=True) - artist_name = models.CharField(max_length=512, blank=True, null=True) + + # Audio bit_rate = models.IntegerField(blank=True, null=True) sample_rate = models.IntegerField(blank=True, null=True) - format = models.CharField(max_length=128, blank=True, null=True) - length = models.DurationField(blank=True, null=True) - album_title = models.CharField(max_length=512, blank=True, null=True) - genre = models.CharField(max_length=64, blank=True, null=True) - comments = models.TextField(blank=True, null=True) - year = models.CharField(max_length=16, blank=True, null=True) - track_number = models.IntegerField(blank=True, null=True) + format = models.CharField(max_length=128, blank=True, null=True) # ? channels = models.IntegerField(blank=True, null=True) - url = models.CharField(max_length=1024, blank=True, null=True) - bpm = models.IntegerField(blank=True, null=True) - rating = models.CharField(max_length=8, blank=True, null=True) - encoded_by = models.CharField(max_length=255, blank=True, null=True) - disc_number = models.CharField(max_length=8, blank=True, null=True) - mood = models.CharField(max_length=64, blank=True, null=True) - label = models.CharField(max_length=512, blank=True, null=True) - composer = models.CharField(max_length=512, blank=True, null=True) - encoder = models.CharField(max_length=64, blank=True, null=True) - checksum = models.CharField(max_length=256, blank=True, null=True) - lyrics = models.TextField(blank=True, null=True) - orchestra = models.CharField(max_length=512, blank=True, null=True) - conductor = models.CharField(max_length=512, blank=True, null=True) - lyricist = models.CharField(max_length=512, blank=True, null=True) - original_lyricist = models.CharField(max_length=512, blank=True, null=True) - radio_station_name = models.CharField(max_length=512, blank=True, null=True) - info_url = models.CharField(max_length=512, blank=True, null=True) - artist_url = models.CharField(max_length=512, blank=True, null=True) - audio_source_url = models.CharField(max_length=512, blank=True, null=True) - radio_station_url = models.CharField(max_length=512, blank=True, null=True) - buy_this_url = models.CharField(max_length=512, blank=True, null=True) - isrc_number = models.CharField(max_length=512, blank=True, null=True) - catalog_number = models.CharField(max_length=512, blank=True, null=True) - original_artist = models.CharField(max_length=512, blank=True, null=True) - copyright = models.CharField(max_length=512, blank=True, null=True) - report_datetime = models.CharField(max_length=32, blank=True, null=True) - report_location = models.CharField(max_length=512, blank=True, null=True) - report_organization = models.CharField(max_length=512, blank=True, null=True) - subject = models.CharField(max_length=512, blank=True, null=True) - contributor = models.CharField(max_length=512, blank=True, null=True) - language = models.CharField(max_length=512, blank=True, null=True) - file_exists = models.BooleanField(blank=True, null=True) + length = models.DurationField(blank=True, null=True) + + bpm = models.IntegerField(blank=True, null=True) # ? replay_gain = models.DecimalField( - max_digits=8, decimal_places=2, blank=True, null=True - ) - owner = models.ForeignKey( - "core.User", - on_delete=models.DO_NOTHING, + max_digits=8, + decimal_places=2, blank=True, null=True, ) - cuein = models.DurationField(blank=True, null=True) - cueout = models.DurationField(blank=True, null=True) - silan_check = models.BooleanField(blank=True, null=True) - hidden = models.BooleanField(blank=True, null=True) - is_scheduled = models.BooleanField(blank=True, null=True) - is_playlist = models.BooleanField(blank=True, null=True) - filesize = models.IntegerField() - description = models.CharField(max_length=512, blank=True, null=True) + cue_in = models.DurationField(blank=True, null=True, db_column="cuein") + cue_out = models.DurationField(blank=True, null=True, db_column="cueout") + + # Metadata + name = models.CharField(max_length=255) # ? + description = models.CharField(max_length=512, blank=True, null=True) # ? + artwork = models.CharField(max_length=512, blank=True, null=True) + artist_name = models.CharField(max_length=512, blank=True, null=True) + artist_url = models.CharField(max_length=512, blank=True, null=True) # ? + original_artist = models.CharField(max_length=512, blank=True, null=True) # ? + album_title = models.CharField(max_length=512, blank=True, null=True) + track_title = models.CharField(max_length=512, blank=True, null=True) + genre = models.CharField(max_length=64, blank=True, null=True) + mood = models.CharField(max_length=64, blank=True, null=True) + date = models.CharField(max_length=16, blank=True, null=True, db_column="year") + track_number = models.IntegerField(blank=True, null=True) + disc_number = models.CharField(max_length=8, blank=True, null=True) # ? + comment = models.TextField(blank=True, null=True, db_column="comments") + language = models.CharField(max_length=512, blank=True, null=True) + label = models.CharField(max_length=512, blank=True, null=True) + copyright = models.CharField(max_length=512, blank=True, null=True) + composer = models.CharField(max_length=512, blank=True, null=True) + conductor = models.CharField(max_length=512, blank=True, null=True) + orchestra = models.CharField(max_length=512, blank=True, null=True) # ? + encoder = models.CharField(max_length=64, blank=True, null=True) + encoded_by = models.CharField(max_length=255, blank=True, null=True) # ? + isrc = models.CharField( + max_length=512, + blank=True, + null=True, + db_column="isrc_number", + ) + + lyrics = models.TextField(blank=True, null=True) # ? + lyricist = models.CharField(max_length=512, blank=True, null=True) # ? + original_lyricist = models.CharField(max_length=512, blank=True, null=True) # ? + + subject = models.CharField(max_length=512, blank=True, null=True) # ? + contributor = models.CharField(max_length=512, blank=True, null=True) # ? + rating = models.CharField(max_length=8, blank=True, null=True) # ? + url = models.CharField(max_length=1024, blank=True, null=True) # ? + info_url = models.CharField(max_length=512, blank=True, null=True) # ? + audio_source_url = models.CharField(max_length=512, blank=True, null=True) # ? + buy_this_url = models.CharField(max_length=512, blank=True, null=True) # ? + catalog_number = models.CharField(max_length=512, blank=True, null=True) # ? + + radio_station_name = models.CharField(max_length=512, blank=True, null=True) # ? + radio_station_url = models.CharField(max_length=512, blank=True, null=True) # ? + + report_datetime = models.CharField(max_length=32, blank=True, null=True) # ? + report_location = models.CharField(max_length=512, blank=True, null=True) # ? + report_organization = models.CharField(max_length=512, blank=True, null=True) # ? + def get_owner(self): return self.owner diff --git a/api/schema.yml b/api/schema.yml index 608f31812..8f40d6398 100644 --- a/api/schema.yml +++ b/api/schema.yml @@ -5838,50 +5838,53 @@ components: type: string nullable: true maxLength: 16 - name: - type: string - maxLength: 255 - mime: - type: string - maxLength: 255 - ftype: - type: string - maxLength: 128 + import_status: + allOf: + - $ref: "#/components/schemas/ImportStatusEnum" + minimum: -2147483648 + maximum: 2147483647 filepath: type: string nullable: true - import_status: + size: type: integer maximum: 2147483647 minimum: -2147483648 - currently_accessing: - type: integer - maximum: 2147483647 - minimum: -2147483648 - mtime: - type: string - format: date-time + exists: + type: boolean nullable: true - utime: + mime: type: string - format: date-time - nullable: true - lptime: - type: string - format: date-time - nullable: true + maxLength: 255 md5: type: string nullable: true maxLength: 32 - track_title: - type: string + hidden: + type: boolean nullable: true - maxLength: 512 - artist_name: - type: string + accessed: + type: integer + maximum: 2147483647 + minimum: -2147483648 + scheduled: + type: boolean + nullable: true + part_of_list: + type: boolean + nullable: true + created_at: + type: string + format: date-time + nullable: true + updated_at: + type: string + format: date-time + nullable: true + last_played_at: + type: string + format: date-time nullable: true - maxLength: 512 bit_rate: type: integer maximum: 2147483647 @@ -5896,21 +5899,70 @@ components: type: string nullable: true maxLength: 128 + channels: + type: integer + maximum: 2147483647 + minimum: -2147483648 + nullable: true length: type: string nullable: true + bpm: + type: integer + maximum: 2147483647 + minimum: -2147483648 + nullable: true + replay_gain: + type: string + format: decimal + pattern: ^-?\d{0,6}(?:\.\d{0,2})?$ + nullable: true + cue_in: + type: string + nullable: true + cue_out: + type: string + nullable: true + name: + type: string + maxLength: 255 + description: + type: string + nullable: true + maxLength: 512 + artwork: + type: string + nullable: true + maxLength: 512 + artist_name: + type: string + nullable: true + maxLength: 512 + artist_url: + type: string + nullable: true + maxLength: 512 + original_artist: + type: string + nullable: true + maxLength: 512 album_title: type: string nullable: true maxLength: 512 + track_title: + type: string + nullable: true + maxLength: 512 genre: type: string nullable: true maxLength: 64 - comments: + mood: type: string nullable: true - year: + maxLength: 64 + date: type: string nullable: true maxLength: 16 @@ -5919,63 +5971,52 @@ components: maximum: 2147483647 minimum: -2147483648 nullable: true - channels: - type: integer - maximum: 2147483647 - minimum: -2147483648 - nullable: true - url: - type: string - nullable: true - maxLength: 1024 - bpm: - type: integer - maximum: 2147483647 - minimum: -2147483648 - nullable: true - rating: - type: string - nullable: true - maxLength: 8 - encoded_by: - type: string - nullable: true - maxLength: 255 disc_number: type: string nullable: true maxLength: 8 - mood: + comment: type: string nullable: true - maxLength: 64 + language: + type: string + nullable: true + maxLength: 512 label: type: string nullable: true maxLength: 512 + copyright: + type: string + nullable: true + maxLength: 512 composer: type: string nullable: true maxLength: 512 - encoder: - type: string - nullable: true - maxLength: 64 - checksum: - type: string - nullable: true - maxLength: 256 - lyrics: - type: string - nullable: true - orchestra: - type: string - nullable: true - maxLength: 512 conductor: type: string nullable: true maxLength: 512 + orchestra: + type: string + nullable: true + maxLength: 512 + encoder: + type: string + nullable: true + maxLength: 64 + encoded_by: + type: string + nullable: true + maxLength: 255 + isrc: + type: string + nullable: true + maxLength: 512 + lyrics: + type: string + nullable: true lyricist: type: string nullable: true @@ -5984,43 +6025,43 @@ components: type: string nullable: true maxLength: 512 - radio_station_name: + subject: type: string nullable: true maxLength: 512 + contributor: + type: string + nullable: true + maxLength: 512 + rating: + type: string + nullable: true + maxLength: 8 + url: + type: string + nullable: true + maxLength: 1024 info_url: type: string nullable: true maxLength: 512 - artist_url: - type: string - nullable: true - maxLength: 512 audio_source_url: type: string nullable: true maxLength: 512 - radio_station_url: - type: string - nullable: true - maxLength: 512 buy_this_url: type: string nullable: true maxLength: 512 - isrc_number: - type: string - nullable: true - maxLength: 512 catalog_number: type: string nullable: true maxLength: 512 - original_artist: + radio_station_name: type: string nullable: true maxLength: 512 - copyright: + radio_station_url: type: string nullable: true maxLength: 512 @@ -6036,73 +6077,27 @@ components: type: string nullable: true maxLength: 512 - subject: - type: string - nullable: true - maxLength: 512 - contributor: - type: string - nullable: true - maxLength: 512 - language: - type: string - nullable: true - maxLength: 512 - file_exists: - type: boolean - nullable: true - replay_gain: - type: string - format: decimal - pattern: ^-?\d{0,6}(?:\.\d{0,2})?$ - nullable: true - cuein: - type: string - nullable: true - cueout: - type: string - nullable: true - silan_check: - type: boolean - nullable: true - hidden: - type: boolean - nullable: true - is_scheduled: - type: boolean - nullable: true - is_playlist: - type: boolean - nullable: true - filesize: - type: integer - maximum: 2147483647 - minimum: -2147483648 - description: - type: string - nullable: true - maxLength: 512 - artwork: - type: string - nullable: true - maxLength: 512 - edited_by: - type: string - format: uri - nullable: true owner: type: string format: uri nullable: true + edited_by: + type: string + format: uri + nullable: true required: - - currently_accessing - - filesize - - ftype + - accessed - id - - import_status - item_url - mime - name + - size + ImportStatusEnum: + enum: + - 0 + - 1 + - 2 + type: integer ImportedPodcast: type: object properties: @@ -6283,50 +6278,53 @@ components: type: string nullable: true maxLength: 16 - name: - type: string - maxLength: 255 - mime: - type: string - maxLength: 255 - ftype: - type: string - maxLength: 128 + import_status: + allOf: + - $ref: "#/components/schemas/ImportStatusEnum" + minimum: -2147483648 + maximum: 2147483647 filepath: type: string nullable: true - import_status: + size: type: integer maximum: 2147483647 minimum: -2147483648 - currently_accessing: - type: integer - maximum: 2147483647 - minimum: -2147483648 - mtime: - type: string - format: date-time + exists: + type: boolean nullable: true - utime: + mime: type: string - format: date-time - nullable: true - lptime: - type: string - format: date-time - nullable: true + maxLength: 255 md5: type: string nullable: true maxLength: 32 - track_title: - type: string + hidden: + type: boolean nullable: true - maxLength: 512 - artist_name: - type: string + accessed: + type: integer + maximum: 2147483647 + minimum: -2147483648 + scheduled: + type: boolean + nullable: true + part_of_list: + type: boolean + nullable: true + created_at: + type: string + format: date-time + nullable: true + updated_at: + type: string + format: date-time + nullable: true + last_played_at: + type: string + format: date-time nullable: true - maxLength: 512 bit_rate: type: integer maximum: 2147483647 @@ -6341,21 +6339,70 @@ components: type: string nullable: true maxLength: 128 + channels: + type: integer + maximum: 2147483647 + minimum: -2147483648 + nullable: true length: type: string nullable: true + bpm: + type: integer + maximum: 2147483647 + minimum: -2147483648 + nullable: true + replay_gain: + type: string + format: decimal + pattern: ^-?\d{0,6}(?:\.\d{0,2})?$ + nullable: true + cue_in: + type: string + nullable: true + cue_out: + type: string + nullable: true + name: + type: string + maxLength: 255 + description: + type: string + nullable: true + maxLength: 512 + artwork: + type: string + nullable: true + maxLength: 512 + artist_name: + type: string + nullable: true + maxLength: 512 + artist_url: + type: string + nullable: true + maxLength: 512 + original_artist: + type: string + nullable: true + maxLength: 512 album_title: type: string nullable: true maxLength: 512 + track_title: + type: string + nullable: true + maxLength: 512 genre: type: string nullable: true maxLength: 64 - comments: + mood: type: string nullable: true - year: + maxLength: 64 + date: type: string nullable: true maxLength: 16 @@ -6364,63 +6411,52 @@ components: maximum: 2147483647 minimum: -2147483648 nullable: true - channels: - type: integer - maximum: 2147483647 - minimum: -2147483648 - nullable: true - url: - type: string - nullable: true - maxLength: 1024 - bpm: - type: integer - maximum: 2147483647 - minimum: -2147483648 - nullable: true - rating: - type: string - nullable: true - maxLength: 8 - encoded_by: - type: string - nullable: true - maxLength: 255 disc_number: type: string nullable: true maxLength: 8 - mood: + comment: type: string nullable: true - maxLength: 64 + language: + type: string + nullable: true + maxLength: 512 label: type: string nullable: true maxLength: 512 + copyright: + type: string + nullable: true + maxLength: 512 composer: type: string nullable: true maxLength: 512 - encoder: - type: string - nullable: true - maxLength: 64 - checksum: - type: string - nullable: true - maxLength: 256 - lyrics: - type: string - nullable: true - orchestra: - type: string - nullable: true - maxLength: 512 conductor: type: string nullable: true maxLength: 512 + orchestra: + type: string + nullable: true + maxLength: 512 + encoder: + type: string + nullable: true + maxLength: 64 + encoded_by: + type: string + nullable: true + maxLength: 255 + isrc: + type: string + nullable: true + maxLength: 512 + lyrics: + type: string + nullable: true lyricist: type: string nullable: true @@ -6429,43 +6465,43 @@ components: type: string nullable: true maxLength: 512 - radio_station_name: + subject: type: string nullable: true maxLength: 512 + contributor: + type: string + nullable: true + maxLength: 512 + rating: + type: string + nullable: true + maxLength: 8 + url: + type: string + nullable: true + maxLength: 1024 info_url: type: string nullable: true maxLength: 512 - artist_url: - type: string - nullable: true - maxLength: 512 audio_source_url: type: string nullable: true maxLength: 512 - radio_station_url: - type: string - nullable: true - maxLength: 512 buy_this_url: type: string nullable: true maxLength: 512 - isrc_number: - type: string - nullable: true - maxLength: 512 catalog_number: type: string nullable: true maxLength: 512 - original_artist: + radio_station_name: type: string nullable: true maxLength: 512 - copyright: + radio_station_url: type: string nullable: true maxLength: 512 @@ -6481,61 +6517,11 @@ components: type: string nullable: true maxLength: 512 - subject: - type: string - nullable: true - maxLength: 512 - contributor: - type: string - nullable: true - maxLength: 512 - language: - type: string - nullable: true - maxLength: 512 - file_exists: - type: boolean - nullable: true - replay_gain: - type: string - format: decimal - pattern: ^-?\d{0,6}(?:\.\d{0,2})?$ - nullable: true - cuein: - type: string - nullable: true - cueout: - type: string - nullable: true - silan_check: - type: boolean - nullable: true - hidden: - type: boolean - nullable: true - is_scheduled: - type: boolean - nullable: true - is_playlist: - type: boolean - nullable: true - filesize: - type: integer - maximum: 2147483647 - minimum: -2147483648 - description: - type: string - nullable: true - maxLength: 512 - artwork: - type: string - nullable: true - maxLength: 512 - edited_by: + owner: type: string format: uri nullable: true - owner: + edited_by: type: string format: uri nullable: true