· 4 years ago · Jul 21, 2021, 10:52 AM
1import {
2 Character,
3 filterCharacters,
4 getCharacter,
5 getMultipleCharactersByUrls,
6 listCharacters
7} from "./api/characters"
8import {Episode} from "./api/episodes.d";
9import {getEpisode, listEpisodes} from "./api/episodes";
10import {ApiResponseList} from "./api/apiGeneric";
11import {getLocation, Location} from "./api/locations";
12
13
14
15
16
17// Level 1 (avg. time/task = ?) 2pts
18
19/**
20 * Funkcja powinna zwracać podstawowe informacje o bohaterze w formie kilku linijkowego stringa:
21 * Name: Rick Sanchez
22 * Gender: Male
23 * Status: Alive
24 * Location: undefined
25 *
26 * Niestety, błędnie zwraca wartość dla Location.
27 */
28
29// points: 1 / refactor: 1 ( +0.5 za deep destructuring, 0.5 za użycie op. "?")
30function basicCharacterInfo(character: Character): string {
31
32 return "Name: " + character.name + "\n" + "Gender: " + character.gender + "\n" + "Status: " + character.status + "\n"
33 + "Location: " + (character.location[0] && character.location[0].name);
34
35 // refactor:
36 // const {name, gender, status, location: {name: locationName}} = character;
37
38 // return `Name: ${name}
39 // Gender: ${gender}
40 // Status: ${status}
41 // Location: ${locationName}`;
42}
43
44// (async function basicCharacterData_TEST() {
45// const sampleCharacter = await getCharacter(1);
46// console.log(basicCharacterInfo(sampleCharacter));
47// })()
48
49/**
50 * Funkcja rozszerza obiekt typu Character o dodatkowe pole isDead typu boolean
51 */
52interface CharacterWithDeadInfo extends Character {
53 isDead: boolean;
54}
55
56// points: 2 (mniejsza il. za nie uzycie map) lub bł. impl
57function decorateCharactersWithIsDeadInfo(characters: Array<Character>): Array<CharacterWithDeadInfo> {
58 // @ts-ignore TODO: Implement
59 // return characters;
60 // solution:
61 return characters.map((character: Character) => ({
62 ...character,
63 isDead: (character.status === "Dead")
64 }))
65}
66
67// (async function decorateCharactersWithIsDeadInfo__TEST() {
68// const {results: characters} = await listCharacters();
69// const charactersWithDeadInfos = decorateCharactersWithIsDeadInfo(characters);
70// const albertEinstein = charactersWithDeadInfos[10];
71// // console.log(`charactersWithDeadInfos: `, charactersWithDeadInfos)
72// console.log(`${albertEinstein?.name} should be dead, is he? -> ${albertEinstein?.isDead}`)
73// })()
74
75
76
77
78
79// Level 2
80
81/**
82 * Funkcja pobiera listę bohaterów z API, wybiera z odpowiedzi API tablice wyników (results), aby ostatecznie zwrócić tablice ich imion.
83 * Analiza kodu powinna pozwolić na jego uproszczenie.
84 */
85// punkty: 0 / refaktor: 3
86function listCharacterNames(): Promise<Array<string>> {
87 return new Promise((resolve, reject) => {
88 listCharacters()
89 .then(({results}) => results.map(item => item.name))
90 .then(CharacterNames => resolve(CharacterNames))
91 });
92
93 // refactor:
94 // const characters = await listCharacters();
95 // return characters.map(({name}) => name)
96
97}
98
99// (async function listCharacterNames__TEST() {
100// listCharacterNames().then(console.log)
101// })()
102
103
104/**
105 * Funkcja powinna zwracać pierwszego bohatera który nie jest gatunku
106 * ludzkiego, ale pochodzi z planety ziemskiej. W wypadku gdy nie znajdzie
107 * takiego bohatera powinna zwracać null.
108 *
109 * @param cL Lista bohaterów
110 */
111// punkty: 1 (zwracanie null zamiast "none") / refactor: 2
112function getSpecialChar(cL: Array<Character>): Character | null {
113
114 // Some-dev: I had to use any type, otherwise I got some errors
115 var specialChar: any = "none";
116 cL.forEach(function (C) {
117 if (C.species !== "Human") {
118 if (C.origin && C.origin.name.startsWith("Earth")) {
119 specialChar = C;
120 return;
121 }
122 }
123 })
124
125 return specialChar;
126 // refactor:
127 // -rename function & vars
128 // return characters
129 // .find(character => (character.species !== "Human" && character.origin?.startsWith("Earth")))
130}
131
132// (async function getSpecialChar__TEST() {
133// const {results: characters} = await listCharacters(3);
134// const firstNonHumanFromEarth = getSpecialChar(characters);
135// console.log(`firstNonHumanFromEarth: `, firstNonHumanFromEarth)
136// })();
137
138/**
139 * Funkcja przyjmuje tablice bohaterów, odrzucająć z niej wszystkie istoty ludzkie
140 * @param źródłowe_postacie
141 * REFACTOR
142 */
143// punkty: 0 / refactor: 3 (uproscić warunek, refaktor konwencji nazw, użycie filter)
144function filterToNotHumanCharacters(źródłowe_postacie: Array<Character>) {
145 let NieLudzie = [];
146 for (const postać of źródłowe_postacie) {
147 if (typeof postać.type === "string" && postać && postać?.species !== "Human")
148 NieLudzie.push(postać);
149 }
150 return NieLudzie;
151
152 // refactor:
153 // return characters.filter((character) => character?.species !== "Human");
154}
155
156// (async function filterToNotHumanCharacters__TEST() {
157// const {results: characters} = await listCharacters();
158// const nonHumans = filterToNotHumanCharacters(characters);
159// console.log(`nonHumans: `, nonHumans)
160// })()
161
162
163
164
165
166
167// Level 3 (avg. time/task = ?);
168// 3 ex., junior: 1; +mid: 2
169
170interface CharacterWithEpisode extends Character {
171 firstEpisode: Episode
172}
173
174/**
175 * Funkcja rozszerza obiekt bohatera o dodatkowe pole pierwszego epizodu w którym zagrał,
176 * jeśli bohater nie istnieje funkcja powinna zwrócić null
177 * @param character Bohater
178 */
179// punkty: 6
180async function characterWithFirstEpisode(characterId: number): Promise<CharacterWithEpisode> {
181 // solution:
182 try {
183 const character = await getCharacter(characterId);
184 const firstEpisode = await getEpisode(character.episode[0]);
185 return {
186 ...character,
187 firstEpisode
188 };
189 } catch (err) {
190 return null;
191 }
192}
193// (async function characterWithFirstEpisode__TEST() {
194// const sampleCharacter = await getCharacter(1);
195// const existingCharacter = await characterWithFirstEpisode(1)
196// const nonExistingCharacter = await characterWithFirstEpisode(1529)
197// console.log({nonExistingCharacter, existingCharacter});
198// })()
199
200
201/**
202 * Funkcja rozszerza zadanego bohatera o pola location i lastEpisode, które mają przyjąć pełne obiekty Location i Episode
203 */
204// punkty: 5 (-2 za nie użycie Promise.all)
205
206interface characterWithLocationAndLastEp extends Character {
207 location: Location,
208 lastEpisode: Episode
209}
210
211async function getCharacterLocationAndLastEpisode(character: Character): Promise<characterWithLocationAndLastEp> {
212
213 // solution:
214 const {location: {url: locationUrl}} = character;
215 const [lastEpisodeUrl] = character.episode.slice(-1);
216 const [location, lastEpisode] = await Promise.all([
217 getLocation(locationUrl),
218 getEpisode(lastEpisodeUrl)
219 ])
220 return {
221 ...character,
222 location,
223 lastEpisode
224 }
225}
226
227// (async function getCharacterLocationAndLastEpisode__TEST() {
228// const sampleCharacter = await getCharacter(38);
229// console.log(`sampleCharacter: `,
230// await getCharacterLocationAndLastEpisode(sampleCharacter));
231// })()
232
233/**
234 * Funkcja przyjmuje obiekt zawierające pewne pola typu Character,
235 * transformuje je odpowiednio aby uzyskać parametry URL jak np. "?name=rick&status=alive"
236 * @param characterFilter
237 */
238// punkty: 6
239async function filterCharactersByObject(characterFilter: Partial<Character>): Promise<ApiResponseList<Character>> {
240 // solution:
241 // const filterQuery = Object.entries(characterFilter)
242 // .map(([key, val]) => `${key}=${val}`)
243 // .join("&")
244 // return filterCharacters("?"+filterQuery);
245}
246
247// (async function filterCharactersByObject__TEST() {
248// const allDeadPeople = await filterCharactersByObject({
249// type: "Human",
250// status: "Dead"
251// });
252// console.log(`allDeadPeople: `, allDeadPeople)
253// })()
254
255
256
257
258
259// Level 4 (avg. time/task = ?); 9pts
260// junior: 0; +mid: 1; TODO: 1 (0)
261
262type SeasonNames = "S01" | "S02" | "S03" | "S04" | "S05";
263type EpisodesBySeasons = {
264 [key in SeasonNames]?: Array<Episode>
265}
266
267/**
268 * Funkcja ma za zadanie np. otrzymanej listy epizodów zbudować obiekt którego kluczami
269 * sa nazwy sezonów (SeasonNames) a wartościami tablice epizodów należace do tego sezonu
270 * @param episodes
271 */
272// punkty: 7
273async function groupEpisodesBySeason(episodes: Array<Episode>): Promise<EpisodesBySeasons> {
274 function _getSeason(episode: Episode): SeasonNames {
275 return episode.episode.split("E")[0] as SeasonNames;
276 }
277
278 return episodes.reduce((episodesBySeasons, episode: Episode) => {
279 const episodeSeason = _getSeason(episode); // Or just direct operation, w/o function
280 const seasonEpisodes = episodesBySeasons[episodeSeason] || [];
281 return {
282 ...episodesBySeasons,
283 [episodeSeason]: [...seasonEpisodes, episode]
284 }
285 }, {});
286}
287
288// (async function groupEpisodesBySeason__TEST() {
289// const {results: allEpisodes} = await listEpisodes();
290// const seasonEpisodes = await groupEpisodesBySeason(allEpisodes);
291//
292// // Test if it works:
293// const secondSeasonEpisodeFive = seasonEpisodes.S02[4];
294// console.log(`secondSeasonEpisodeFive: `, secondSeasonEpisodeFive)
295// })()
296
297
298type EpisodeWithCharacters = Episode & {
299 characters: Array<Character>
300}
301
302/**
303 * Funkcja przyjmuje liste epizodów, a docelowo ma rozszerzyć każdy epizod o pole
304 * characters -> czyli o dokładne dane (obiekt Character) bohaterów
305 * każdego bohatera wystepującego w epizodzie
306 *
307 * Tip: należy użyć getMultipleCharactersByUrls z api/characters.ts
308 * @param episodes
309 */
310// punkty: 10; -4 za nie uzycie słownika bohaterów po url
311async function episodesWithAllCharacters(episodes: Array<Episode>): Promise<Array<EpisodeWithCharacters>> {
312
313 const referredCharactersUrls: Set<string> = episodes.reduce((urlsSet: Set<string>, ep: Episode) => {
314 ep.characters.forEach(urlsSet.add, urlsSet);
315 return urlsSet;
316 }, new Set());
317
318 const characters = await getMultipleCharactersByUrls([...referredCharactersUrls])
319 const charactersByUrls = await characters.reduce((acc, character: Character) => ({
320 ...acc,
321 [character.url]: character
322 }), {})
323
324 return episodes.map((episode: Episode) => ({
325 ...episode,
326 characters: episode.characters.map(url => charactersByUrls[url])
327 }))
328}
329
330// (async function episodesWithAllCharacters__TEST() {
331// const {results: allEpisodes} = await listEpisodes();
332// const sampleEpisodes = [allEpisodes[1], allEpisodes[10], allEpisodes[19]];
333// const episodesWithCharacters = await episodesWithAllCharacters(sampleEpisodes);
334// console.log("episodes with characters: ", episodesWithCharacters);
335// console.log("\n\n\nmore details of a single item: ", episodesWithCharacters[2])
336// })()
337