-
{{ entryData.overview }}
+
{{ displayData?.overview }}
diff --git a/src/libs/apis/tmdb/constants.ts b/src/libs/apis/tmdb/constants.ts
index 73b9363..d7587dd 100644
--- a/src/libs/apis/tmdb/constants.ts
+++ b/src/libs/apis/tmdb/constants.ts
@@ -7,3 +7,260 @@ export const DEFAULT_SEARCH_MOVIE_PARAMS = {
query: "",
region: "en-US",
} satisfies TmdbMovieSearchQueryParams;
+
+interface Iso31661Alpha2 {
+ code: string;
+ name: string;
+}
+
+export const ISO_3166_1_ALPHA_2: readonly Iso31661Alpha2[] = [
+ { code: "AF", name: "Afghanistan" },
+ { code: "AL", name: "Albania" },
+ { code: "DZ", name: "Algeria" },
+ { code: "AS", name: "American Samoa" },
+ { code: "AD", name: "Andorra" },
+ { code: "AO", name: "Angola" },
+ { code: "AI", name: "Anguilla" },
+ { code: "AQ", name: "Antarctica" },
+ { code: "AG", name: "Antigua and Barbuda" },
+ { code: "AR", name: "Argentina" },
+ { code: "AM", name: "Armenia" },
+ { code: "AW", name: "Aruba" },
+ { code: "AU", name: "Australia" },
+ { code: "AT", name: "Austria" },
+ { code: "AZ", name: "Azerbaijan" },
+ { code: "BS", name: "Bahamas" },
+ { code: "BH", name: "Bahrain" },
+ { code: "BD", name: "Bangladesh" },
+ { code: "BB", name: "Barbados" },
+ { code: "BY", name: "Belarus" },
+ { code: "BE", name: "Belgium" },
+ { code: "BZ", name: "Belize" },
+ { code: "BJ", name: "Benin" },
+ { code: "BM", name: "Bermuda" },
+ { code: "BT", name: "Bhutan" },
+ { code: "BO", name: "Bolivia, Plurinational State of" },
+ { code: "BQ", name: "Bonaire, Sint Eustatius and Saba" },
+ { code: "BA", name: "Bosnia and Herzegovina" },
+ { code: "BW", name: "Botswana" },
+ { code: "BV", name: "Bouvet Island" },
+ { code: "BR", name: "Brazil" },
+ { code: "IO", name: "British Indian Ocean Territory" },
+ { code: "BN", name: "Brunei Darussalam" },
+ { code: "BG", name: "Bulgaria" },
+ { code: "BF", name: "Burkina Faso" },
+ { code: "BI", name: "Burundi" },
+ { code: "KH", name: "Cambodia" },
+ { code: "CM", name: "Cameroon" },
+ { code: "CA", name: "Canada" },
+ { code: "CV", name: "Cape Verde" },
+ { code: "KY", name: "Cayman Islands" },
+ { code: "CF", name: "Central African Republic" },
+ { code: "TD", name: "Chad" },
+ { code: "CL", name: "Chile" },
+ { code: "CN", name: "China" },
+ { code: "CX", name: "Christmas Island" },
+ { code: "CC", name: "Cocos (Keeling) Islands" },
+ { code: "CO", name: "Colombia" },
+ { code: "KM", name: "Comoros" },
+ { code: "CG", name: "Congo" },
+ { code: "CD", name: "Congo, the Democratic Republic of the" },
+ { code: "CK", name: "Cook Islands" },
+ { code: "CR", name: "Costa Rica" },
+ { code: "HR", name: "Croatia" },
+ { code: "CU", name: "Cuba" },
+ { code: "CW", name: "Curaçao" },
+ { code: "CY", name: "Cyprus" },
+ { code: "CZ", name: "Czech Republic" },
+ { code: "CI", name: "Côte d'Ivoire" },
+ { code: "DK", name: "Denmark" },
+ { code: "DJ", name: "Djibouti" },
+ { code: "DM", name: "Dominica" },
+ { code: "DO", name: "Dominican Republic" },
+ { code: "EC", name: "Ecuador" },
+ { code: "EG", name: "Egypt" },
+ { code: "SV", name: "El Salvador" },
+ { code: "GQ", name: "Equatorial Guinea" },
+ { code: "ER", name: "Eritrea" },
+ { code: "EE", name: "Estonia" },
+ { code: "SZ", name: "Eswatini" },
+ { code: "ET", name: "Ethiopia" },
+ { code: "FK", name: "Falkland Islands (Malvinas)" },
+ { code: "FO", name: "Faroe Islands" },
+ { code: "FJ", name: "Fiji" },
+ { code: "FI", name: "Finland" },
+ { code: "FR", name: "France" },
+ { code: "GF", name: "French Guiana" },
+ { code: "PF", name: "French Polynesia" },
+ { code: "TF", name: "French Southern Territories" },
+ { code: "GA", name: "Gabon" },
+ { code: "GM", name: "Gambia" },
+ { code: "GE", name: "Georgia" },
+ { code: "DE", name: "Germany" },
+ { code: "GH", name: "Ghana" },
+ { code: "GI", name: "Gibraltar" },
+ { code: "GR", name: "Greece" },
+ { code: "GL", name: "Greenland" },
+ { code: "GD", name: "Grenada" },
+ { code: "GP", name: "Guadeloupe" },
+ { code: "GU", name: "Guam" },
+ { code: "GT", name: "Guatemala" },
+ { code: "GG", name: "Guernsey" },
+ { code: "GN", name: "Guinea" },
+ { code: "GW", name: "Guinea-Bissau" },
+ { code: "GY", name: "Guyana" },
+ { code: "HT", name: "Haiti" },
+ { code: "HM", name: "Heard Island and McDonald Islands" },
+ { code: "VA", name: "Holy See (Vatican City State)" },
+ { code: "HN", name: "Honduras" },
+ { code: "HK", name: "Hong Kong" },
+ { code: "HU", name: "Hungary" },
+ { code: "IS", name: "Iceland" },
+ { code: "IN", name: "India" },
+ { code: "ID", name: "Indonesia" },
+ { code: "IR", name: "Iran, Islamic Republic of" },
+ { code: "IQ", name: "Iraq" },
+ { code: "IE", name: "Ireland" },
+ { code: "IM", name: "Isle of Man" },
+ { code: "IL", name: "Israel" },
+ { code: "IT", name: "Italy" },
+ { code: "JM", name: "Jamaica" },
+ { code: "JP", name: "Japan" },
+ { code: "JE", name: "Jersey" },
+ { code: "JO", name: "Jordan" },
+ { code: "KZ", name: "Kazakhstan" },
+ { code: "KE", name: "Kenya" },
+ { code: "KI", name: "Kiribati" },
+ { code: "KP", name: "Korea, Democratic People's Republic of" },
+ { code: "KR", name: "Korea, Republic of" },
+ { code: "KW", name: "Kuwait" },
+ { code: "KG", name: "Kyrgyzstan" },
+ { code: "LA", name: "Lao People's Democratic Republic" },
+ { code: "LV", name: "Latvia" },
+ { code: "LB", name: "Lebanon" },
+ { code: "LS", name: "Lesotho" },
+ { code: "LR", name: "Liberia" },
+ { code: "LY", name: "Libya" },
+ { code: "LI", name: "Liechtenstein" },
+ { code: "LT", name: "Lithuania" },
+ { code: "LU", name: "Luxembourg" },
+ { code: "MO", name: "Macao" },
+ { code: "MK", name: "Macedonia, the Former Yugoslav Republic of" },
+ { code: "MG", name: "Madagascar" },
+ { code: "MW", name: "Malawi" },
+ { code: "MY", name: "Malaysia" },
+ { code: "MV", name: "Maldives" },
+ { code: "ML", name: "Mali" },
+ { code: "MT", name: "Malta" },
+ { code: "MH", name: "Marshall Islands" },
+ { code: "MQ", name: "Martinique" },
+ { code: "MR", name: "Mauritania" },
+ { code: "MU", name: "Mauritius" },
+ { code: "YT", name: "Mayotte" },
+ { code: "MX", name: "Mexico" },
+ { code: "FM", name: "Micronesia, Federated States of" },
+ { code: "MD", name: "Moldova, Republic of" },
+ { code: "MC", name: "Monaco" },
+ { code: "MN", name: "Mongolia" },
+ { code: "ME", name: "Montenegro" },
+ { code: "MS", name: "Montserrat" },
+ { code: "MA", name: "Morocco" },
+ { code: "MZ", name: "Mozambique" },
+ { code: "MM", name: "Myanmar" },
+ { code: "NA", name: "Namibia" },
+ { code: "NR", name: "Nauru" },
+ { code: "NP", name: "Nepal" },
+ { code: "NL", name: "Netherlands" },
+ { code: "NC", name: "New Caledonia" },
+ { code: "NZ", name: "New Zealand" },
+ { code: "NI", name: "Nicaragua" },
+ { code: "NE", name: "Niger" },
+ { code: "NG", name: "Nigeria" },
+ { code: "NU", name: "Niue" },
+ { code: "NF", name: "Norfolk Island" },
+ { code: "MP", name: "Northern Mariana Islands" },
+ { code: "NO", name: "Norway" },
+ { code: "OM", name: "Oman" },
+ { code: "PK", name: "Pakistan" },
+ { code: "PW", name: "Palau" },
+ { code: "PS", name: "Palestine, State of" },
+ { code: "PA", name: "Panama" },
+ { code: "PG", name: "Papua New Guinea" },
+ { code: "PY", name: "Paraguay" },
+ { code: "PE", name: "Peru" },
+ { code: "PH", name: "Philippines" },
+ { code: "PN", name: "Pitcairn" },
+ { code: "PL", name: "Poland" },
+ { code: "PT", name: "Portugal" },
+ { code: "PR", name: "Puerto Rico" },
+ { code: "QA", name: "Qatar" },
+ { code: "RO", name: "Romania" },
+ { code: "RU", name: "Russian Federation" },
+ { code: "RW", name: "Rwanda" },
+ { code: "RE", name: "Réunion" },
+ { code: "BL", name: "Saint Barthélemy" },
+ { code: "SH", name: "Saint Helena, Ascension and Tristan da Cunha" },
+ { code: "KN", name: "Saint Kitts and Nevis" },
+ { code: "LC", name: "Saint Lucia" },
+ { code: "MF", name: "Saint Martin (French part)" },
+ { code: "PM", name: "Saint Pierre and Miquelon" },
+ { code: "VC", name: "Saint Vincent and the Grenadines" },
+ { code: "WS", name: "Samoa" },
+ { code: "SM", name: "San Marino" },
+ { code: "ST", name: "Sao Tome and Principe" },
+ { code: "SA", name: "Saudi Arabia" },
+ { code: "SN", name: "Senegal" },
+ { code: "RS", name: "Serbia" },
+ { code: "SC", name: "Seychelles" },
+ { code: "SL", name: "Sierra Leone" },
+ { code: "SG", name: "Singapore" },
+ { code: "SX", name: "Sint Maarten (Dutch part)" },
+ { code: "SK", name: "Slovakia" },
+ { code: "SI", name: "Slovenia" },
+ { code: "SB", name: "Solomon Islands" },
+ { code: "SO", name: "Somalia" },
+ { code: "ZA", name: "South Africa" },
+ { code: "GS", name: "South Georgia and the South Sandwich Islands" },
+ { code: "SS", name: "South Sudan" },
+ { code: "ES", name: "Spain" },
+ { code: "LK", name: "Sri Lanka" },
+ { code: "SD", name: "Sudan" },
+ { code: "SR", name: "Suriname" },
+ { code: "SJ", name: "Svalbard and Jan Mayen" },
+ { code: "SE", name: "Sweden" },
+ { code: "CH", name: "Switzerland" },
+ { code: "SY", name: "Syrian Arab Republic" },
+ { code: "TW", name: "Taiwan, Province of China" },
+ { code: "TJ", name: "Tajikistan" },
+ { code: "TZ", name: "Tanzania, United Republic of" },
+ { code: "TH", name: "Thailand" },
+ { code: "TL", name: "Timor-Leste" },
+ { code: "TG", name: "Togo" },
+ { code: "TK", name: "Tokelau" },
+ { code: "TO", name: "Tonga" },
+ { code: "TT", name: "Trinidad and Tobago" },
+ { code: "TN", name: "Tunisia" },
+ { code: "TR", name: "Turkey" },
+ { code: "TM", name: "Turkmenistan" },
+ { code: "TC", name: "Turks and Caicos Islands" },
+ { code: "TV", name: "Tuvalu" },
+ { code: "UG", name: "Uganda" },
+ { code: "UA", name: "Ukraine" },
+ { code: "AE", name: "United Arab Emirates" },
+ { code: "GB", name: "United Kingdom" },
+ { code: "US", name: "United States" },
+ { code: "UM", name: "United States Minor Outlying Islands" },
+ { code: "UY", name: "Uruguay" },
+ { code: "UZ", name: "Uzbekistan" },
+ { code: "VU", name: "Vanuatu" },
+ { code: "VE", name: "Venezuela, Bolivarian Republic of" },
+ { code: "VN", name: "Viet Nam" },
+ { code: "VG", name: "Virgin Islands, British" },
+ { code: "VI", name: "Virgin Islands, U.S." },
+ { code: "WF", name: "Wallis and Futuna" },
+ { code: "EH", name: "Western Sahara" },
+ { code: "YE", name: "Yemen" },
+ { code: "ZM", name: "Zambia" },
+ { code: "ZW", name: "Zimbabwe" },
+ { code: "AX", name: "Åland Islands" },
+];
diff --git a/src/libs/apis/tmdb/schemas.ts b/src/libs/apis/tmdb/schemas.ts
index 020a18a..2f3a5a3 100644
--- a/src/libs/apis/tmdb/schemas.ts
+++ b/src/libs/apis/tmdb/schemas.ts
@@ -2,7 +2,9 @@ import { Schema } from "effect";
import { TmdbCompany } from "./schemas/companies";
import { TmdbCredit } from "./schemas/credits";
+import { DateFromTmdbDate } from "./schemas/dates";
import { TmdbLanguage } from "./schemas/languages";
+import { ArtWorkTitle } from "./schemas/works";
// Requête
@@ -45,12 +47,12 @@ export class TmdbMovieSearchResponseResult
genre_ids: Schema.Array(Schema.NonNegativeInt),
id: Schema.NonNegativeInt,
original_language: Schema.String,
- original_title: Schema.String,
+ original_title: Schema.String.pipe(Schema.fromBrand(ArtWorkTitle)),
overview: Schema.String,
popularity: Schema.Number,
poster_path: Schema.Union(Schema.String, Schema.Null),
- release_date: Schema.String,
- title: Schema.String,
+ release_date: DateFromTmdbDate,
+ title: Schema.String.pipe(Schema.fromBrand(ArtWorkTitle)),
video: Schema.Boolean,
vote_average: Schema.Number,
vote_count: Schema.NonNegativeInt,
@@ -82,18 +84,18 @@ export class TmdbMovieDetailsWithCreditsResponse
id: Schema.NonNegativeInt,
imdb_id: Schema.Union(Schema.String, Schema.Null),
original_language: Schema.String,
- original_title: Schema.String,
+ original_title: Schema.String.pipe(Schema.fromBrand(ArtWorkTitle)),
overview: Schema.String,
popularity: Schema.Number,
poster_path: Schema.Union(Schema.String, Schema.Null),
production_companies: Schema.Array(TmdbCompany),
- release_date: Schema.String,
+ release_date: DateFromTmdbDate,
revenue: Schema.NonNegativeInt,
runtime: Schema.NonNegativeInt,
spoken_languages: Schema.Array(TmdbLanguage),
status: Schema.NonEmptyString,
tagline: Schema.String,
- title: Schema.NonEmptyString,
+ title: Schema.String.pipe(Schema.fromBrand(ArtWorkTitle)),
video: Schema.Boolean,
vote_average: Schema.Number,
vote_count: Schema.NonNegativeInt,
diff --git a/src/libs/apis/tmdb/schemas/companies.ts b/src/libs/apis/tmdb/schemas/companies.ts
index 3f8c907..500ea91 100644
--- a/src/libs/apis/tmdb/schemas/companies.ts
+++ b/src/libs/apis/tmdb/schemas/companies.ts
@@ -1,12 +1,19 @@
-import { Brand, Schema } from "effect";
+import { Brand, Schema, String as Str } from "effect";
import { CountryIsoCode } from "./countries";
/** L'ID d'une société sous forme de nombre entier positif. */
export type CompanyId = Brand.Brand<"CompanyId"> & number;
export const CompanyId = Brand.refined(
- n => Number.isInteger(n) && n >= 0,
- n => Brand.error(`${String(n)} doit être un nombre entier positif.`),
+ (id: number) => Number.isInteger(id) && id >= 0,
+ (id: number) => Brand.error(`${String(id)} doit être un nombre entier positif.`),
+);
+
+/** Le nom d'une société. */
+export type CompanyName = Brand.Brand<"CompanyName"> & string;
+export const CompanyName = Brand.refined(
+ (name: string) => Str.isString(name) && Str.isNonEmpty(name),
+ (name: string) => Brand.error(`${name} n'est pas un nom valide de société.`),
);
/** Entité représentant une société (p. ex. de production). */
@@ -16,7 +23,7 @@ export class TmdbCompany extends Schema.Class("TmdbCompany")({
/** Chemin relatif du logo de la société. */
logo_path: Schema.Union(Schema.String, Schema.Null),
/** Nom de la société. */
- name: Schema.String,
- /** Pays d'origine de la société sous forme de code ISO 3166-1. */
+ name: Schema.NonEmptyString.pipe(Schema.fromBrand(CompanyName)),
+ /** Pays d'origine de la société sous forme de code ISO 3166-1 Alpha 2. */
origin_country: Schema.Union(Schema.String.pipe(Schema.fromBrand(CountryIsoCode)), Schema.String),
}) {}
diff --git a/src/libs/apis/tmdb/schemas/countries.ts b/src/libs/apis/tmdb/schemas/countries.ts
index 90d7d1f..3fac3f5 100644
--- a/src/libs/apis/tmdb/schemas/countries.ts
+++ b/src/libs/apis/tmdb/schemas/countries.ts
@@ -1,14 +1,24 @@
-import { Brand, Schema } from "effect";
+import { Array as Arr, Brand, Schema, String } from "effect";
-// TODO: Vérifier le code ISO dans une liste.
-/** Le code ISO 3166-1 (à deux lettres) d'un pays. */
+import { ISO_3166_1_ALPHA_2 } from "../constants";
+
+/** Le code ISO 3166-1 Alpha 2 (à deux lettres) d'un pays. */
export type CountryIsoCode = Brand.Brand & string;
export const CountryIsoCode = Brand.refined(
- s => s.length == 2,
- s => Brand.error(`${s} ne correspond pas au code ISO 3166-1 connu d'un pays.`),
+ (code: string) => code.length == 2 && Arr.some(ISO_3166_1_ALPHA_2, iso => iso.code === code),
+ (code: string) => Brand.error(`${code} ne correspond pas au code ISO 3166-1 connu d'un pays.`),
);
+export type CountryName = Brand.Brand & string;
+export const CountryName = Brand.refined(
+ (name: string) => String.isString(name) && Arr.some(ISO_3166_1_ALPHA_2, iso => iso.name === name),
+ (name: string) => Brand.error(`${name} n'est pas un nom connu de pays.`),
+);
+
+/** Un pays selon l'API TMDB. */
export class TmdbCountry extends Schema.Class("TmdbCountry")({
+ /** Code ISO 3166-1 Alpha 2 du pays. */
iso_3166_1: Schema.NonEmptyString.pipe(Schema.fromBrand(CountryIsoCode)),
- name: Schema.String,
+ /** Nom du pays. */
+ name: Schema.NonEmptyString.pipe(Schema.fromBrand(CountryName)),
}) {}
diff --git a/src/libs/apis/tmdb/schemas/credits.ts b/src/libs/apis/tmdb/schemas/credits.ts
index 1cb0dc1..ae0c5f3 100644
--- a/src/libs/apis/tmdb/schemas/credits.ts
+++ b/src/libs/apis/tmdb/schemas/credits.ts
@@ -1,66 +1,61 @@
-import type { Values } from "@/libs/utils/types";
+import { Brand, Schema, String as Str } from "effect";
-import { Brand, Match, Schema } from "effect";
+import { GenderStringFromInteger, PersonId, PersonName } from "./persons";
-export const PERSON_GENDER_VALUES = {
- FEMALE: "female",
- MALE: "male",
- NON_BINARY: "non-binary",
- OTHER: "other",
-} as const;
-export type PersonGender = Values;
-
-const GenderStringFromInteger = Schema.transform(
- Schema.Literal(0, 1, 2, 3),
- Schema.Enums(PERSON_GENDER_VALUES),
- {
- decode: (number: 0 | 1 | 2 | 3) =>
- Match.value(number).pipe(
- Match.when(0, () => PERSON_GENDER_VALUES.OTHER),
- Match.when(1, () => PERSON_GENDER_VALUES.FEMALE),
- Match.when(2, () => PERSON_GENDER_VALUES.MALE),
- Match.when(3, () => PERSON_GENDER_VALUES.NON_BINARY),
- Match.exhaustive,
- ),
- encode: (gender: PersonGender) =>
- Match.value(gender).pipe(
- Match.when(PERSON_GENDER_VALUES.OTHER, () => 0 as const),
- Match.when(PERSON_GENDER_VALUES.FEMALE, () => 1 as const),
- Match.when(PERSON_GENDER_VALUES.MALE, () => 2 as const),
- Match.when(PERSON_GENDER_VALUES.NON_BINARY, () => 3 as const),
- Match.exhaustive,
- ),
- strict: true,
- },
+/** L'ID de participation au casting sous forme de nombre entier positif. */
+export type CastId = Brand.Brand<"CastId"> & number;
+export const CastId = Brand.refined(
+ (id: number) => Number.isInteger(id) && id >= 0,
+ (id: number) => Brand.error(`${String(id)} doit être un nombre entier positif.`),
);
-/** L'ID d'une personne sous forme de nombre entier positif. */
-export type PersonId = Brand.Brand<"PersonId"> & number;
-export const PersonId = Brand.refined(
- n => Number.isInteger(n) && n >= 0,
- n => Brand.error(`${String(n)} doit être un nombre entier positif.`),
+/** L'ID d'accréditation sous forme de chaîne alphanumérique. */
+export type CreditId = Brand.Brand<"CreditId"> & string;
+export const CreditId = Brand.refined(
+ (id: string) => Str.isString(id) && Str.length(id) === 24,
+ (id: string) => Brand.error(`${String(id)} doit être une chaîne alphanumérique de 24 caractères.`),
);
-/** Le nom d'une personne. */
-export type PersonName = Brand.Brand<"PersonName"> & string;
-export const PersonName = Brand.refined(
- s => s.length > 2,
- s => Brand.error(`${String(s)} doit contenir au moins 2 lettres.`),
+/** Nom du personnage. */
+export type CharacterName = Brand.Brand<"CharacterName"> & string;
+export const CharacterName = Brand.refined(
+ (name: string) => Str.isString(name),
+ (name: string) => Brand.error(`${name} n'est pas un nom de personnage valide`),
);
+export interface MovieMainCrew {
+ directors: TmdbCredit[];
+ editors: TmdbCredit[];
+ writers: TmdbCredit[];
+}
+
export class TmdbCredit extends Schema.Class("TmdbCredit")({
+ /** Concerne un film pour adultes. */
adult: Schema.Boolean,
- cast_id: Schema.NonNegativeInt.pipe(Schema.optional),
- character: Schema.NonEmptyString.pipe(Schema.optional),
- credit_id: Schema.String,
+ /** ID de participation au casting (son ordre d'ajout à l'entrée TMDB). */
+ cast_id: Schema.NonNegativeInt.pipe(Schema.fromBrand(CastId), Schema.optional),
+ /** Nom du personnage joué par la personne. */
+ character: Schema.String.pipe(Schema.fromBrand(CharacterName), Schema.optional),
+ /** ID unique de l'accréditation. */
+ credit_id: Schema.NonEmptyString.pipe(Schema.fromBrand(CreditId)),
+ /** Département auprès duquel l'accréditation prend place. */
department: Schema.String.pipe(Schema.optional),
+ /** Genre de la personne. */
gender: GenderStringFromInteger,
+ /** ID de la personne. */
id: Schema.NonNegativeInt.pipe(Schema.fromBrand(PersonId)),
+ /** Rôle de la personne. */
job: Schema.NonEmptyString.pipe(Schema.optional),
+ /** Département historiquement associé à la personne. */
known_for_department: Schema.String,
+ /** Nom de la personne. */
name: Schema.NonEmptyString.pipe(Schema.fromBrand(PersonName)),
+ /** Ordre au sein du casting. */
order: Schema.NonNegativeInt.pipe(Schema.optional),
+ /** Nom de naissance de la personne. */
original_name: Schema.NonEmptyString.pipe(Schema.fromBrand(PersonName)),
+ /** Popularité TMDB de la personne. */
popularity: Schema.Number,
+ /** Chemin relatif TMDB du profil de la personne. */
profile_path: Schema.Union(Schema.String, Schema.Null),
}) {}
diff --git a/src/libs/apis/tmdb/schemas/dates.ts b/src/libs/apis/tmdb/schemas/dates.ts
new file mode 100644
index 0000000..b7f0df8
--- /dev/null
+++ b/src/libs/apis/tmdb/schemas/dates.ts
@@ -0,0 +1,16 @@
+import type { Utc } from "effect/DateTime";
+
+import { DateTime, Schema } from "effect";
+
+/** Schéma acceptant une chaîne de caractère au format "YYYY-MM-DD" et la convertissant en DateTime. */
+export const DateFromTmdbDate = Schema.transform(
+ Schema.String.pipe(Schema.length(10)),
+ Schema.DateTimeUtcFromSelf,
+ {
+ decode: (string: string): Utc => DateTime.unsafeMake(string),
+ encode: (date: Utc): string => DateTime.format(date, { dateStyle: "short", locale: "sv-SE" }),
+ strict: true,
+ },
+);
+
+export const YearString = Schema.String.pipe(Schema.pattern(/^$|[0-9]{4}/));
diff --git a/src/libs/apis/tmdb/schemas/jobs.ts b/src/libs/apis/tmdb/schemas/jobs.ts
new file mode 100644
index 0000000..d2a8abc
--- /dev/null
+++ b/src/libs/apis/tmdb/schemas/jobs.ts
@@ -0,0 +1,32 @@
+import { Brand, String as Str } from "effect";
+
+/** Rôle dans une oeuvre. */
+export type Job = Brand.Brand<"Job"> & string;
+export const Job = Brand.refined(
+ (name: string) => Str.isString(name) && Str.length(name) >= 2,
+ (name: string) => Brand.error(`${name} n'est pas un rôle connu.`),
+);
+
+const JOBS = {
+ DIRECTOR: "Director",
+ EDITOR: "Editor",
+ NOVEL: "Novel",
+ ORIGINAL_SCREENPLAY: "Original Screenplay",
+ SCREENPLAY: "Screenplay",
+ WRITER: "Writer",
+};
+
+export const DIRECTING_MAIN_JOBS = [
+ JOBS.DIRECTOR,
+];
+
+export const EDITING_MAIN_JOBS = [
+ JOBS.EDITOR,
+];
+
+export const WRITING_MAIN_JOBS = [
+ JOBS.NOVEL,
+ JOBS.ORIGINAL_SCREENPLAY,
+ JOBS.SCREENPLAY,
+ JOBS.WRITER,
+];
diff --git a/src/libs/apis/tmdb/schemas/languages.ts b/src/libs/apis/tmdb/schemas/languages.ts
index a13645b..756ad3f 100644
--- a/src/libs/apis/tmdb/schemas/languages.ts
+++ b/src/libs/apis/tmdb/schemas/languages.ts
@@ -2,8 +2,8 @@ import { Brand, Schema } from "effect";
export type LanguageIsoCode = Brand.Brand & string;
export const LanguageIsoCode = Brand.refined(
- s => /[a-z]{2}/.test(s),
- s => Brand.error(`${s} ne correspond pas au code ISO 639-1 connu d'une langue.`),
+ (code: string) => /[a-z]{2}/.test(code),
+ (code: string) => Brand.error(`${code} ne correspond pas au code ISO 639-1 connu d'une langue.`),
);
export class TmdbLanguage extends Schema.Class("TmdbLanguage")({
diff --git a/src/libs/apis/tmdb/schemas/persons.ts b/src/libs/apis/tmdb/schemas/persons.ts
new file mode 100644
index 0000000..cc72ec1
--- /dev/null
+++ b/src/libs/apis/tmdb/schemas/persons.ts
@@ -0,0 +1,50 @@
+import type { Values } from "@/libs/utils/types";
+
+import { Brand, Match, Schema, String as Str } from "effect";
+
+export const PERSON_GENDER_VALUES = {
+ FEMALE: "female",
+ MALE: "male",
+ NON_BINARY: "non-binary",
+ OTHER: "other",
+} as const;
+export type PersonGender = Values;
+
+/** Transformation de Schéma d'index numérique vers valeur textuelle de l'énumération de Genre. */
+export const GenderStringFromInteger = Schema.transform(
+ Schema.Literal(0, 1, 2, 3),
+ Schema.Enums(PERSON_GENDER_VALUES),
+ {
+ decode: (number: 0 | 1 | 2 | 3) =>
+ Match.value(number).pipe(
+ Match.when(0, () => PERSON_GENDER_VALUES.OTHER),
+ Match.when(1, () => PERSON_GENDER_VALUES.FEMALE),
+ Match.when(2, () => PERSON_GENDER_VALUES.MALE),
+ Match.when(3, () => PERSON_GENDER_VALUES.NON_BINARY),
+ Match.exhaustive,
+ ),
+ encode: (gender: PersonGender) =>
+ Match.value(gender).pipe(
+ Match.when(PERSON_GENDER_VALUES.OTHER, () => 0 as const),
+ Match.when(PERSON_GENDER_VALUES.FEMALE, () => 1 as const),
+ Match.when(PERSON_GENDER_VALUES.MALE, () => 2 as const),
+ Match.when(PERSON_GENDER_VALUES.NON_BINARY, () => 3 as const),
+ Match.exhaustive,
+ ),
+ strict: true,
+ },
+);
+
+/** L'ID d'une personne sous forme de nombre entier positif. */
+export type PersonId = Brand.Brand<"PersonId"> & number;
+export const PersonId = Brand.refined(
+ (id: number) => Number.isInteger(id) && id >= 0,
+ (id: number) => Brand.error(`${String(id)} doit être un nombre entier positif.`),
+);
+
+/** Le nom d'une personne. */
+export type PersonName = Brand.Brand<"PersonName"> & string;
+export const PersonName = Brand.refined(
+ (name: string) => Str.isString(name) && Str.length(name) >= 2,
+ (name: string) => Brand.error(`${String(name)} n'est pas un nom valide'.`),
+);
diff --git a/src/libs/apis/tmdb/schemas/works.ts b/src/libs/apis/tmdb/schemas/works.ts
new file mode 100644
index 0000000..6b63736
--- /dev/null
+++ b/src/libs/apis/tmdb/schemas/works.ts
@@ -0,0 +1,8 @@
+import { Brand, String as Str } from "effect";
+
+/** Titre d'une oeuvre. */
+export type ArtWorkTitle = Brand.Brand<"ArtWorkTitle"> & string;
+export const ArtWorkTitle = Brand.refined(
+ (name: string) => Str.isString(name) && Str.length(name) >= 2,
+ (name: string) => Brand.error(`${name} n'est pas un titre de d'oeuvre valide.`),
+);
diff --git a/src/libs/search/schemas.ts b/src/libs/search/schemas.ts
index 83522ae..12caa3c 100644
--- a/src/libs/search/schemas.ts
+++ b/src/libs/search/schemas.ts
@@ -1,10 +1,14 @@
import { APPRECIATION_STATES, MEDIA_TYPES } from "@/db/schemas/constants";
import { Schema } from "effect";
+import { YearString } from "../apis/tmdb/schemas/dates";
+import { LanguageIsoCode } from "../apis/tmdb/schemas/languages";
+import { ArtWorkTitle } from "../apis/tmdb/schemas/works";
+
export class SearchPageQueryParams extends Schema.Class("SearchPageQueryParams")({
query: Schema.NonEmptyString,
type: Schema.Enums(MEDIA_TYPES),
- year: Schema.String,
+ year: YearString,
}) {}
export class MergedTmdbLocalData extends Schema.Class("MergedTmdbLocalData")({
@@ -18,14 +22,14 @@ export class MergedTmdbLocalData extends Schema.Class("Merg
entryId: Schema.NonNegativeInt.pipe(Schema.optional),
entryStateId: Schema.NonNegativeInt.pipe(Schema.optional),
genreIds: Schema.Array(Schema.NonNegativeInt),
- originalLanguage: Schema.String,
+ originalLanguage: Schema.String.pipe(Schema.fromBrand(LanguageIsoCode)),
originalResultIndex: Schema.Int,
- originalTitle: Schema.String,
+ originalTitle: Schema.String.pipe(Schema.fromBrand(ArtWorkTitle)),
overview: Schema.String,
popularity: Schema.Number,
posterBlob: Schema.Unknown.pipe(Schema.optional),
posterUrl: Schema.Union(Schema.String, Schema.Null),
releaseDate: Schema.String,
- title: Schema.String,
+ title: Schema.String.pipe(Schema.fromBrand(ArtWorkTitle)),
tmdbId: Schema.NonNegativeInt,
}) {}
diff --git a/src/pages/SearchPage.vue b/src/pages/SearchPage.vue
index e09f148..70eaeaf 100644
--- a/src/pages/SearchPage.vue
+++ b/src/pages/SearchPage.vue
@@ -20,10 +20,10 @@
import { ReadApi } from "@/services/read-api.ts";
import { RuntimeClient } from "@/services/runtime-client.ts";
import { TmdbApi } from "@/services/tmdb-api.ts";
- import { Effect, pipe, Schema } from "effect";
+ import { useEntryStore } from "@/stores/entry.ts";
+ import { DateTime, Effect, pipe, Schema } from "effect";
import { computed, onMounted, ref, useTemplateRef, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
- import { useEntryStore } from "@/stores/entry.ts";
// États
@@ -33,18 +33,22 @@
const route = useRoute();
const router = useRouter();
- /** Effet dérivé des paramètres validés de la route. */
- const routeQueryParams = computed(() => Schema.decodeUnknown(SearchPageQueryParams)(route.query));
+ // Magasins
+ const entryStore = useEntryStore();
/** L'Élément DOM du formulaire de recherche. */
const form = useTemplateRef("form");
/** Valeurs du formulaire de recherche. */
const searchFormData: Ref = ref();
+ /** Effet dérivé des paramètres validés de la route. */
+ const routeQueryParams = computed(() => Schema.decodeUnknown(SearchPageQueryParams)(route.query));
/** Retour de la requête de recherche de films auprès de l'API TMDB. */
const search: Ref = ref();
/** Données complètes de la recherche avec les données TMDB et locales. */
const searchData: Ref
-
+