diff --git a/Metron Comicbook Database.yaml b/Metron Comicbook Database.yaml index a20c057..78b6235 100644 --- a/Metron Comicbook Database.yaml +++ b/Metron Comicbook Database.yaml @@ -1825,6 +1825,10 @@ paths: update: Update a publisher's information. parameters: + - in: query + name: alt_names + schema: + type: string - in: query name: cv_id schema: @@ -1851,6 +1855,11 @@ paths: description: A page number within the paginated result set. schema: type: integer + - in: query + name: q + schema: + type: string + description: Quick search across name and alternative names tags: - api security: @@ -2433,6 +2442,89 @@ paths: - yaml - in: query name: lang + schema: + type: string + enum: + - en + - it + tags: + - api + security: + - basicAuth: [] + - cookieAuth: [] + - knoxApiToken: [] + - {} + responses: + '200': + content: + application/vnd.oai.openapi: + schema: + type: object + additionalProperties: {} + application/yaml: + schema: + type: object + additionalProperties: {} + application/vnd.oai.openapi+json: + schema: + type: object + additionalProperties: {} + application/json: + schema: + type: object + additionalProperties: {} + description: '' + /api/series/: + get: + operationId: api_series_list + description: |- + list: + Returns a list of all the comic series. + + retrieve: + Returns the information of an individual comic series. + + create: + Add a new Series. + + update: + Update a Series information. + parameters: + - in: query + name: alt_names + schema: + type: string + - in: query + name: character_id + schema: + type: integer + description: Character Metron ID + - in: query + name: creator_id + schema: + type: integer + description: Creator Metron ID + - in: query + name: cv_id + schema: + type: integer + description: Comic Vine ID + - in: query + name: gcd_id + schema: + type: integer + description: Grand Comics Database ID + - in: query + name: imprint_id + schema: + type: integer + description: Imprint Metron ID + - in: query + name: imprint_name + schema: + type: string + - in: query + name: language schema: type: string enum: @@ -2536,82 +2628,107 @@ paths: - vi - zh-hans - zh-hant - tags: - - api - security: - - basicAuth: [] - - cookieAuth: [] - - knoxApiToken: [] - - {} - responses: - '200': - content: - application/vnd.oai.openapi: - schema: - type: object - additionalProperties: {} - application/yaml: - schema: - type: object - additionalProperties: {} - application/vnd.oai.openapi+json: - schema: - type: object - additionalProperties: {} - application/json: - schema: - type: object - additionalProperties: {} - description: '' - /api/series/: - get: - operationId: api_series_list - description: |- - list: - Returns a list of all the comic series. - - retrieve: - Returns the information of an individual comic series. - - create: - Add a new Series. - - update: - Update a Series information. - parameters: - - in: query - name: alt_names - schema: - type: string - - in: query - name: character_id - schema: - type: integer - description: Character Metron ID - - in: query - name: creator_id - schema: - type: integer - description: Creator Metron ID - - in: query - name: cv_id - schema: - type: integer - description: Comic Vine ID - - in: query - name: gcd_id - schema: - type: integer - description: Grand Comics Database ID - - in: query - name: imprint_id - schema: - type: integer - description: Imprint Metron ID - - in: query - name: imprint_name - schema: - type: string + description: |- + * `af` - Afrikaans + * `ar` - Arabic + * `ar-dz` - Algerian Arabic + * `ast` - Asturian + * `az` - Azerbaijani + * `bg` - Bulgarian + * `be` - Belarusian + * `bn` - Bengali + * `br` - Breton + * `bs` - Bosnian + * `ca` - Catalan + * `ckb` - Central Kurdish (Sorani) + * `cs` - Czech + * `cy` - Welsh + * `da` - Danish + * `de` - German + * `dsb` - Lower Sorbian + * `el` - Greek + * `en` - English + * `en-au` - Australian English + * `en-gb` - British English + * `eo` - Esperanto + * `es` - Spanish + * `es-ar` - Argentinian Spanish + * `es-co` - Colombian Spanish + * `es-mx` - Mexican Spanish + * `es-ni` - Nicaraguan Spanish + * `es-ve` - Venezuelan Spanish + * `et` - Estonian + * `eu` - Basque + * `fa` - Persian + * `fi` - Finnish + * `fr` - French + * `fy` - Frisian + * `ga` - Irish + * `gd` - Scottish Gaelic + * `gl` - Galician + * `he` - Hebrew + * `hi` - Hindi + * `hr` - Croatian + * `hsb` - Upper Sorbian + * `ht` - Haitian Creole + * `hu` - Hungarian + * `hy` - Armenian + * `ia` - Interlingua + * `id` - Indonesian + * `ig` - Igbo + * `io` - Ido + * `is` - Icelandic + * `it` - Italian + * `ja` - Japanese + * `ka` - Georgian + * `kab` - Kabyle + * `kk` - Kazakh + * `km` - Khmer + * `kn` - Kannada + * `ko` - Korean + * `ky` - Kyrgyz + * `lb` - Luxembourgish + * `lt` - Lithuanian + * `lv` - Latvian + * `mk` - Macedonian + * `ml` - Malayalam + * `mn` - Mongolian + * `mr` - Marathi + * `ms` - Malay + * `my` - Burmese + * `nb` - Norwegian Bokmål + * `ne` - Nepali + * `nl` - Dutch + * `nn` - Norwegian Nynorsk + * `os` - Ossetic + * `pa` - Punjabi + * `pl` - Polish + * `pt` - Portuguese + * `pt-br` - Brazilian Portuguese + * `ro` - Romanian + * `ru` - Russian + * `sk` - Slovak + * `sl` - Slovenian + * `sq` - Albanian + * `sr` - Serbian + * `sr-latn` - Serbian Latin + * `sv` - Swedish + * `sw` - Swahili + * `ta` - Tamil + * `te` - Telugu + * `tg` - Tajik + * `th` - Thai + * `tk` - Turkmen + * `tr` - Turkish + * `tt` - Tatar + * `udm` - Udmurt + * `ug` - Uyghur + * `uk` - Ukrainian + * `ur` - Urdu + * `uz` - Uzbek + * `vi` - Vietnamese + * `zh-hans` - Simplified Chinese + * `zh-hant` - Traditional Chinese - in: query name: missing_cv_id schema: @@ -4866,10 +4983,14 @@ components: enum: - USD - GBP + - EUR + - ITL type: string description: |- * `USD` - USD * `GBP` - GBP + * `EUR` - EUR + * `ITL` - ITL DesiredGradeEnum: enum: - 10.0 @@ -5493,6 +5614,10 @@ components: items: $ref: '#/components/schemas/Genre' readOnly: true + language: + oneOf: + - $ref: '#/components/schemas/LanguageEnum' + - $ref: '#/components/schemas/BlankEnum' required: - genres - id @@ -5501,6 +5626,210 @@ components: - sort_name - volume - year_began + LanguageEnum: + enum: + - af + - ar + - ar-dz + - ast + - az + - bg + - be + - bn + - br + - bs + - ca + - ckb + - cs + - cy + - da + - de + - dsb + - el + - en + - en-au + - en-gb + - eo + - es + - es-ar + - es-co + - es-mx + - es-ni + - es-ve + - et + - eu + - fa + - fi + - fr + - fy + - ga + - gd + - gl + - he + - hi + - hr + - hsb + - ht + - hu + - hy + - ia + - id + - ig + - io + - is + - it + - ja + - ka + - kab + - kk + - km + - kn + - ko + - ky + - lb + - lt + - lv + - mk + - ml + - mn + - mr + - ms + - my + - nb + - ne + - nl + - nn + - os + - pa + - pl + - pt + - pt-br + - ro + - ru + - sk + - sl + - sq + - sr + - sr-latn + - sv + - sw + - ta + - te + - tg + - th + - tk + - tr + - tt + - udm + - ug + - uk + - ur + - uz + - vi + - zh-hans + - zh-hant + type: string + description: |- + * `af` - Afrikaans + * `ar` - Arabic + * `ar-dz` - Algerian Arabic + * `ast` - Asturian + * `az` - Azerbaijani + * `bg` - Bulgarian + * `be` - Belarusian + * `bn` - Bengali + * `br` - Breton + * `bs` - Bosnian + * `ca` - Catalan + * `ckb` - Central Kurdish (Sorani) + * `cs` - Czech + * `cy` - Welsh + * `da` - Danish + * `de` - German + * `dsb` - Lower Sorbian + * `el` - Greek + * `en` - English + * `en-au` - Australian English + * `en-gb` - British English + * `eo` - Esperanto + * `es` - Spanish + * `es-ar` - Argentinian Spanish + * `es-co` - Colombian Spanish + * `es-mx` - Mexican Spanish + * `es-ni` - Nicaraguan Spanish + * `es-ve` - Venezuelan Spanish + * `et` - Estonian + * `eu` - Basque + * `fa` - Persian + * `fi` - Finnish + * `fr` - French + * `fy` - Frisian + * `ga` - Irish + * `gd` - Scottish Gaelic + * `gl` - Galician + * `he` - Hebrew + * `hi` - Hindi + * `hr` - Croatian + * `hsb` - Upper Sorbian + * `ht` - Haitian Creole + * `hu` - Hungarian + * `hy` - Armenian + * `ia` - Interlingua + * `id` - Indonesian + * `ig` - Igbo + * `io` - Ido + * `is` - Icelandic + * `it` - Italian + * `ja` - Japanese + * `ka` - Georgian + * `kab` - Kabyle + * `kk` - Kazakh + * `km` - Khmer + * `kn` - Kannada + * `ko` - Korean + * `ky` - Kyrgyz + * `lb` - Luxembourgish + * `lt` - Lithuanian + * `lv` - Latvian + * `mk` - Macedonian + * `ml` - Malayalam + * `mn` - Mongolian + * `mr` - Marathi + * `ms` - Malay + * `my` - Burmese + * `nb` - Norwegian Bokmål + * `ne` - Nepali + * `nl` - Dutch + * `nn` - Norwegian Nynorsk + * `os` - Ossetic + * `pa` - Punjabi + * `pl` - Polish + * `pt` - Portuguese + * `pt-br` - Brazilian Portuguese + * `ro` - Romanian + * `ru` - Russian + * `sk` - Slovak + * `sl` - Slovenian + * `sq` - Albanian + * `sr` - Serbian + * `sr-latn` - Serbian Latin + * `sv` - Swedish + * `sw` - Swahili + * `ta` - Tamil + * `te` - Telugu + * `tg` - Tajik + * `th` - Thai + * `tk` - Turkmen + * `tr` - Turkish + * `tt` - Tatar + * `udm` - Udmurt + * `ug` - Uyghur + * `uk` - Ukrainian + * `ur` - Urdu + * `uz` - Uzbek + * `vi` - Vietnamese + * `zh-hans` - Simplified Chinese + * `zh-hant` - Traditional Chinese MissingIssue: type: object description: Serializer for missing issues in a series. @@ -6412,6 +6741,12 @@ components: name: type: string maxLength: 255 + alt_names: + type: array + items: + type: string + maxLength: 255 + title: Alternative Names founded: type: integer maximum: 32767 @@ -6464,6 +6799,10 @@ components: type: string maxLength: 255 title: Alternative Names + language: + oneOf: + - $ref: '#/components/schemas/LanguageEnum' + - $ref: '#/components/schemas/BlankEnum' volume: type: integer maximum: 32767 @@ -6615,7 +6954,7 @@ components: price: type: string format: decimal - pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + pattern: ^-?\d{0,6}(?:\.\d{0,2})?$ nullable: true sku: type: string @@ -6650,6 +6989,12 @@ components: name: type: string maxLength: 255 + alt_names: + type: array + items: + type: string + maxLength: 255 + title: Alternative Names founded: type: integer maximum: 32767 @@ -7152,6 +7497,10 @@ components: type: string maxLength: 255 title: Alternative Names + language: + oneOf: + - $ref: '#/components/schemas/LanguageEnum' + - $ref: '#/components/schemas/BlankEnum' volume: type: integer maximum: 32767 @@ -7275,6 +7624,10 @@ components: type: string maxLength: 255 title: Alternative Names + language: + oneOf: + - $ref: '#/components/schemas/LanguageEnum' + - $ref: '#/components/schemas/BlankEnum' volume: type: integer maximum: 32767 @@ -7627,7 +7980,7 @@ components: price: type: string format: decimal - pattern: ^-?\d{0,3}(?:\.\d{0,2})?$ + pattern: ^-?\d{0,6}(?:\.\d{0,2})?$ nullable: true sku: type: string diff --git a/src/Metron.Api/Filters/PublisherFilter.cs b/src/Metron.Api/Filters/PublisherFilter.cs index 4800603..9b83773 100644 --- a/src/Metron.Api/Filters/PublisherFilter.cs +++ b/src/Metron.Api/Filters/PublisherFilter.cs @@ -12,6 +12,9 @@ namespace Metron.Api.Filters; public sealed class PublisherFilter { + [QueryParameter("alt_names")] + public string? AltNames { get; set; } + /// Comic Vine ID [QueryParameter("cv_id")] public int? CvId { get; set; } @@ -30,4 +33,8 @@ public sealed class PublisherFilter /// A page number within the paginated result set. [QueryParameter("page")] public int? Page { get; set; } + + /// Quick search across name and alternative names + [QueryParameter("q")] + public string? Q { get; set; } } diff --git a/src/Metron.Api/Filters/SeriesFilter.cs b/src/Metron.Api/Filters/SeriesFilter.cs index e5c5a69..d01d141 100644 --- a/src/Metron.Api/Filters/SeriesFilter.cs +++ b/src/Metron.Api/Filters/SeriesFilter.cs @@ -38,6 +38,10 @@ public sealed class SeriesFilter [QueryParameter("imprint_name")] public string? ImprintName { get; set; } + /// * `af` - Afrikaans + [QueryParameter("language")] + public string? Language { get; set; } + [QueryParameter("missing_cv_id")] public bool? MissingCvId { get; set; } diff --git a/src/Metron.Api/Models/Generated/Enums.cs b/src/Metron.Api/Models/Generated/Enums.cs index 45ad0b1..6bef0e6 100644 --- a/src/Metron.Api/Models/Generated/Enums.cs +++ b/src/Metron.Api/Models/Generated/Enums.cs @@ -552,12 +552,16 @@ public enum CountryEnum /// /// * `USD` - USD /// * `GBP` - GBP +/// * `EUR` - EUR +/// * `ITL` - ITL /// [JsonConverter(typeof(JsonStringEnumConverter))] public enum CurrencyEnum { USD, GBP, + EUR, + ITL, } /// diff --git a/src/Metron.Api/Models/Generated/IssueSeries.cs b/src/Metron.Api/Models/Generated/IssueSeries.cs index c4d5626..c3fd97a 100644 --- a/src/Metron.Api/Models/Generated/IssueSeries.cs +++ b/src/Metron.Api/Models/Generated/IssueSeries.cs @@ -38,4 +38,7 @@ public sealed class IssueSeries [JsonPropertyName("genres")] public List? Genres { get; set; } + + [JsonPropertyName("language")] + public string? Language { get; set; } } diff --git a/src/Metron.Api/Models/Generated/PatchedPublisher.cs b/src/Metron.Api/Models/Generated/PatchedPublisher.cs index 34eb254..2fdc285 100644 --- a/src/Metron.Api/Models/Generated/PatchedPublisher.cs +++ b/src/Metron.Api/Models/Generated/PatchedPublisher.cs @@ -19,6 +19,10 @@ public sealed class PatchedPublisher [JsonPropertyName("name")] public string? Name { get; set; } + /// Alternative Names + [JsonPropertyName("alt_names")] + public List? AltNames { get; set; } + /// Year Founded [JsonPropertyName("founded")] public int? Founded { get; set; } diff --git a/src/Metron.Api/Models/Generated/PatchedSeries.cs b/src/Metron.Api/Models/Generated/PatchedSeries.cs index 44e0b76..170f68c 100644 --- a/src/Metron.Api/Models/Generated/PatchedSeries.cs +++ b/src/Metron.Api/Models/Generated/PatchedSeries.cs @@ -26,6 +26,9 @@ public sealed class PatchedSeries [JsonPropertyName("alt_names")] public List? AltNames { get; set; } + [JsonPropertyName("language")] + public string? Language { get; set; } + /// Volume Number [JsonPropertyName("volume")] public int? Volume { get; set; } diff --git a/src/Metron.Api/Models/Generated/Publisher.cs b/src/Metron.Api/Models/Generated/Publisher.cs index c010e3d..4f75f21 100644 --- a/src/Metron.Api/Models/Generated/Publisher.cs +++ b/src/Metron.Api/Models/Generated/Publisher.cs @@ -19,6 +19,10 @@ public sealed class Publisher [JsonPropertyName("name")] public string? Name { get; set; } + /// Alternative Names + [JsonPropertyName("alt_names")] + public List? AltNames { get; set; } + /// Year Founded [JsonPropertyName("founded")] public int? Founded { get; set; } diff --git a/src/Metron.Api/Models/Generated/Series.cs b/src/Metron.Api/Models/Generated/Series.cs index b6cd659..6145058 100644 --- a/src/Metron.Api/Models/Generated/Series.cs +++ b/src/Metron.Api/Models/Generated/Series.cs @@ -26,6 +26,9 @@ public sealed class Series [JsonPropertyName("alt_names")] public List? AltNames { get; set; } + [JsonPropertyName("language")] + public string? Language { get; set; } + /// Volume Number [JsonPropertyName("volume")] public int? Volume { get; set; } diff --git a/src/Metron.Api/Models/Generated/SeriesRead.cs b/src/Metron.Api/Models/Generated/SeriesRead.cs index 9336bd5..fd2cbc5 100644 --- a/src/Metron.Api/Models/Generated/SeriesRead.cs +++ b/src/Metron.Api/Models/Generated/SeriesRead.cs @@ -26,6 +26,9 @@ public sealed class SeriesRead [JsonPropertyName("alt_names")] public List? AltNames { get; set; } + [JsonPropertyName("language")] + public string? Language { get; set; } + /// Volume Number [JsonPropertyName("volume")] public int? Volume { get; set; } diff --git a/tests/Metron.Api.Tests/ClientIntegrationTests.cs b/tests/Metron.Api.Tests/ClientIntegrationTests.cs index 3be0f38..63fd9ca 100644 --- a/tests/Metron.Api.Tests/ClientIntegrationTests.cs +++ b/tests/Metron.Api.Tests/ClientIntegrationTests.cs @@ -142,4 +142,63 @@ public async Task Collection_RemoveAsync_SendsDeleteRequest() Assert.Equal(HttpMethod.Delete, request.Method); Assert.Equal("https://metron.cloud/api/collection/7/", request.RequestUri!.ToString()); } + + [Fact] + public async Task Publisher_ListAsync_BuildsQueryStringFromAltNamesAndQuickSearchFilter() + { + var fake = new FakeHttpMessageHandler(); + fake.Enqueue(FakeHttpMessageHandler.Json(HttpStatusCode.OK, """ + {"count": 0, "next": null, "previous": null, "results": []} + """)); + + using var client = CreateClient(fake); + await client.Publisher.ListAsync(new PublisherFilter { AltNames = "DC", Q = "comics" }); + + var request = Assert.Single(fake.Requests); + Assert.Contains("alt_names=DC", request.RequestUri!.Query); + Assert.Contains("q=comics", request.RequestUri!.Query); + } + + [Fact] + public async Task Publisher_GetAsync_DeserializesAltNames() + { + var fake = new FakeHttpMessageHandler(); + fake.Enqueue(FakeHttpMessageHandler.Json(HttpStatusCode.OK, """ + {"id": 1, "name": "DC Comics", "alt_names": ["National Comics", "DC"], "modified": "2024-01-01T00:00:00Z"} + """)); + + using var client = CreateClient(fake); + var publisher = await client.Publisher.GetAsync(1); + + Assert.Equal(["National Comics", "DC"], publisher.AltNames); + } + + [Fact] + public async Task Series_ListAsync_BuildsQueryStringFromLanguageFilter() + { + var fake = new FakeHttpMessageHandler(); + fake.Enqueue(FakeHttpMessageHandler.Json(HttpStatusCode.OK, """ + {"count": 0, "next": null, "previous": null, "results": []} + """)); + + using var client = CreateClient(fake); + await client.Series.ListAsync(new SeriesFilter { Language = "it" }); + + var request = Assert.Single(fake.Requests); + Assert.Contains("language=it", request.RequestUri!.Query); + } + + [Fact] + public async Task Series_GetAsync_DeserializesLanguage() + { + var fake = new FakeHttpMessageHandler(); + fake.Enqueue(FakeHttpMessageHandler.Json(HttpStatusCode.OK, """ + {"id": 1, "name": "Amazing Spider-Man", "language": "it", "modified": "2024-01-01T00:00:00Z"} + """)); + + using var client = CreateClient(fake); + var series = await client.Series.GetAsync(1); + + Assert.Equal("it", series.Language); + } } diff --git a/tests/Metron.Api.Tests/MultipartFormDataBuilderTests.cs b/tests/Metron.Api.Tests/MultipartFormDataBuilderTests.cs index 1a2c600..ef2d943 100644 --- a/tests/Metron.Api.Tests/MultipartFormDataBuilderTests.cs +++ b/tests/Metron.Api.Tests/MultipartFormDataBuilderTests.cs @@ -62,6 +62,19 @@ public async Task Build_FormatsDateOnlyAsIso() Assert.Contains(("cover_date", "2024-03-15"), parts); } + [Theory] + [InlineData(CurrencyEnum.EUR, "2")] + [InlineData(CurrencyEnum.ITL, "3")] + public async Task Build_SerializesNewCurrencyEnumValues(CurrencyEnum currency, string expected) + { + var item = new AcquireWishListItem { PurchasePriceCurrency = currency }; + + using var content = MultipartFormDataBuilder.Build(item); + var parts = await ReadPartsAsync(content); + + Assert.Contains(("purchase_price_currency", expected), parts); + } + private static async Task> ReadPartsAsync(MultipartFormDataContent content) { var results = new List<(string, string)>(); diff --git a/tests/Metron.Api.Tests/QueryStringBuilderTests.cs b/tests/Metron.Api.Tests/QueryStringBuilderTests.cs index 1d009a1..5ceb03d 100644 --- a/tests/Metron.Api.Tests/QueryStringBuilderTests.cs +++ b/tests/Metron.Api.Tests/QueryStringBuilderTests.cs @@ -54,4 +54,25 @@ public void Build_EscapesSpecialCharacters() Assert.Equal("?name=Spider-Man%20%26%20Friends", query); } + + [Fact] + public void Build_IncludesPublisherAltNamesAndQuickSearchFilters() + { + var filter = new PublisherFilter { AltNames = "DC", Q = "comics" }; + + var query = QueryStringBuilder.Build(filter); + + Assert.Contains("alt_names=DC", query); + Assert.Contains("q=comics", query); + } + + [Fact] + public void Build_IncludesSeriesLanguageFilter() + { + var filter = new SeriesFilter { Language = "en" }; + + var query = QueryStringBuilder.Build(filter); + + Assert.Equal("?language=en", query); + } }