66 Commits

Author SHA1 Message Date
21f3ed77a4 Pass test for Film details and Person component. 2020-12-21 22:20:12 +01:00
2e35d86e1a Load Locations/People/Vehicles from layout on app load 2020-12-21 22:06:36 +01:00
5bab5b2041 Adapt tests 2020-12-21 22:06:05 +01:00
188625a413 Create basic Person component + collect data by getter 2020-12-21 22:05:53 +01:00
a9771243a1 Basic design for Film detail page 2020-12-21 18:28:12 +01:00
540521f236 Pass tests 2020-12-21 14:10:33 +01:00
67680804e2 Collect Film's nested data on mounted (Locations/Vehicles/People) 2020-12-21 13:57:18 +01:00
fffbc87964 Create vuex modules for Films/Vehicles/Locations/People + Test adaptation 2020-12-21 12:01:36 +01:00
d8cfe69fa0 Add basic Vue logic to Vehicles/Locations/People 2020-12-18 15:23:45 +01:00
e7e6ed91f8 Create People/Locations/Vehicles detail pages 2020-12-18 06:23:29 +01:00
1801fd54ae Move store test file to /store folder 2020-12-18 06:19:37 +01:00
c416542449 Merge vuex-store branch 2020-12-18 06:15:29 +01:00
e1619de1cd Refactor films/_id to use vuex 2020-12-18 06:12:39 +01:00
18be371f39 Test mutation + actions 2020-12-18 05:44:23 +01:00
734b924390 Use Vuex store to handle films 2020-12-18 04:28:14 +01:00
24d3a962bd Refactor films module 2020-12-18 04:07:32 +01:00
432ff2ac14 Add dynamic head for Film page 2020-12-18 04:07:32 +01:00
1cf3dba078 Add param validation + Test for Film page 2020-12-18 04:07:32 +01:00
cf59911c4c Create basic film page 2020-12-18 04:07:32 +01:00
3782c90647 Handle responsivness on Grid + Film components 2020-12-18 04:05:43 +01:00
f2ba114d26 Add description in Header + Change title 2020-12-18 04:05:43 +01:00
0ea1c855d2 Replace films page H2 by Studio Ghibli logo 2020-12-18 04:05:43 +01:00
5787c9d8db Refactor Footer module + Add basic tesst file 2020-12-18 04:05:43 +01:00
8a5958dde0 Refactor Header module 2020-12-18 04:05:43 +01:00
0408b8bf63 Refactor Grid module 2020-12-18 04:05:43 +01:00
f937bfcf4c Test Film component 2020-12-18 04:05:43 +01:00
ab465bbe16 Create Film component 2020-12-18 04:05:43 +01:00
214921a6e8 Add Tailwind Typography plugin 2020-12-18 04:05:43 +01:00
10808b66b2 Create Grid component + Test. Index test adaptations 2020-12-18 04:05:43 +01:00
baed9969b2 Add head property 2020-12-18 04:05:43 +01:00
a413b6f637 Improve Page structure and create a redirect middleware to set /films as homepage 2020-12-18 04:05:43 +01:00
a7a30c37df Add styles to H2 2020-12-18 04:05:43 +01:00
111558309e Create Footer component with styles 2020-12-18 04:05:43 +01:00
9394950263 Add proper styles to handle Header's fixed position and main tag to display correctly. 2020-12-18 04:05:43 +01:00
23595f0e58 Test asyncData method. 2020-12-18 04:05:43 +01:00
6941293c14 Collect films data in AsyncData method. 2020-12-18 04:05:43 +01:00
f95a384409 Refactor films module 2020-12-18 03:44:26 +01:00
f2de531fd9 Add dynamic head for Film page 2020-12-18 03:43:22 +01:00
b3afb2b66f Add param validation + Test for Film page 2020-12-18 03:41:47 +01:00
4ae4b4ecd9 Create basic film page 2020-12-18 03:04:27 +01:00
1b36ad6a81 Handle responsivness on Grid + Film components 2020-12-18 02:37:32 +01:00
c77fe19ed1 Add description in Header + Change title 2020-12-18 02:27:15 +01:00
0b7b64231d Replace films page H2 by Studio Ghibli logo 2020-12-18 02:18:01 +01:00
5de7b3d676 Refactor Footer module + Add basic tesst file 2020-12-18 02:07:15 +01:00
cde972e20e Refactor Header module 2020-12-18 02:03:04 +01:00
e1827b3b7d Refactor Grid module 2020-12-18 02:00:39 +01:00
444d3f8326 Test Film component 2020-12-18 01:47:06 +01:00
c08b0575dc Create Film component 2020-12-18 01:31:09 +01:00
62862e6c14 Add Tailwind Typography plugin 2020-12-18 01:22:17 +01:00
bca6a2ed11 Create Grid component + Test. Index test adaptations 2020-12-17 19:31:25 +01:00
e2051c8d74 Add head property 2020-12-17 18:31:55 +01:00
407ef3dcd1 Improve Page structure and create a redirect middleware to set /films as homepage 2020-12-17 18:28:32 +01:00
467eb68e25 Add styles to H2 2020-12-17 18:17:12 +01:00
7935ae750f Create Footer component with styles 2020-12-17 18:15:40 +01:00
eac90cffd6 Merge branch 'films-page' into develop 2020-12-17 17:16:42 +01:00
e8cc9c89ba Add proper styles to handle Header's fixed position and main tag to display correctly. 2020-12-17 17:15:16 +01:00
7b52deccd8 Test asyncData method. 2020-12-17 17:07:22 +01:00
1a86713c00 Collect films data in AsyncData method. 2020-12-17 14:29:26 +01:00
7d60872823 Vuex store init 2020-12-17 14:26:40 +01:00
ea89471cc0 Header tests. 2020-12-17 12:53:19 +01:00
050942c679 Add styles to Header component 2020-12-17 12:43:50 +01:00
c329368906 TailwindCSS basic configuration 2020-12-17 12:17:47 +01:00
2b2528054a Header base component without styles 2020-12-16 13:53:02 +01:00
1278e23aec Basic setup of Axios module + Test Ghibli API connection 2020-12-16 13:17:20 +01:00
80b7c19e27 Basic config of nuxt package, delete defaults. 2020-12-16 12:21:14 +01:00
5328f5eb51 Create Nuxt.js app 2020-12-16 12:07:05 +01:00
54 changed files with 17754 additions and 0 deletions

16
.babelrc Normal file
View File

@@ -0,0 +1,16 @@
{
"env": {
"test": {
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
]
]
}
}
}

13
.editorconfig Normal file
View File

@@ -0,0 +1,13 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

44
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: ci
on:
push:
branches:
- main
- master
pull_request:
branches:
- main
- master
jobs:
ci:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
node: [14]
steps:
- name: Checkout 🛎
uses: actions/checkout@master
- name: Setup node env 🏗
uses: actions/setup-node@v2.1.2
with:
node-version: ${{ matrix.node }}
- name: Cache node_modules 📦
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies 👨🏻‍💻
run: npm ci
- name: Run tests 🧪
run: npm run test

90
.gitignore vendored Normal file
View File

@@ -0,0 +1,90 @@
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# Nuxt generate
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# IDE / Editor
.idea
# Service worker
sw.*
# macOS
.DS_Store
# Vim swap files
*.swp

View File

@@ -1,5 +1,24 @@
# Ghibli API
## Build Setup
```bash
# install dependencies
$ npm install
# serve with hot reload at localhost:3000
$ npm run dev
# build for production and launch server
$ npm run build
$ npm run start
# generate static project
$ npm run generate
```
For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org).
## Description
Using [the Ghibli API](https://ghibliapi.herokuapp.com), you will build an app with the following features:

7
assets/README.md Normal file
View File

@@ -0,0 +1,7 @@
# ASSETS
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your un-compiled assets such as LESS, SASS, or JavaScript.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked).

3
assets/css/tailwind.css Normal file
View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

BIN
assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1,56 @@
<svg
width="80px"
height="80px"
viewBox="0 0 80 80"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<!-- Generator: Sketch 59.1 (86144) - https://sketch.com -->
<title>Icons/Tomatometer &amp; AS/fresh</title>
<desc>Created with Sketch.</desc>
<defs>
<polygon
id="path-1"
points="0.000109100102 0.246970954 77.0827837 0.246970954 77.0827837 63.7145228 0.000109100102 63.7145228"
/>
</defs>
<g
id="Icons/Tomatometer-&amp;-AS/fresh"
stroke="none"
stroke-width="1"
fill="none"
fill-rule="evenodd"
>
<g id="Group">
<rect
id="Rectangle-Copy-2"
fill="#000000"
opacity="0"
x="0"
y="0"
width="80"
height="80"
/>
<g id="RT_Fresh_Tomato_RGB-(1)" transform="translate(1.327801, 0.000000)">
<g id="Group-3" transform="translate(0.000000, 16.265560)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1" />
</mask>
<g id="Clip-2" />
<path
d="M77.0137759,27.0426556 C76.2423237,14.6741909 69.9521992,5.42041494 60.4876349,0.246970954 C60.5414108,0.548381743 60.273195,0.925145228 59.9678008,0.791701245 C53.7772614,-1.91634855 43.2753527,6.84780083 35.9365975,2.25825726 C35.9917012,3.90539419 35.6700415,11.940249 24.3515353,12.4063071 C24.0843154,12.4172614 23.9372614,12.1443983 24.1062241,11.9512033 C25.619917,10.2247303 27.1482158,5.85360996 24.9507054,3.5233195 C20.2446473,7.74041494 17.5117012,9.32746888 8.48829876,7.23319502 C2.71103734,13.2740249 -0.562655602,21.5419087 0.08,31.8413278 C1.39120332,52.86639 21.0848133,64.8846473 40.9165145,63.6471369 C60.746888,62.4106224 78.3253112,48.0677178 77.0137759,27.0426556"
id="Fill-1"
fill="#FA320A"
mask="url(#mask-2)"
/>
</g>
<path
d="M40.8717012,11.4648963 C44.946722,10.49361 56.6678838,11.3702905 60.4232365,16.3518672 C60.6486307,16.6506224 60.3312863,17.2159336 59.9678008,17.0572614 C53.7772614,14.3492116 43.2753527,23.113361 35.9365975,18.5238174 C35.9917012,20.1709544 35.6700415,28.2058091 24.3515353,28.6718672 C24.0843154,28.6828216 23.9372614,28.4099585 24.1062241,28.2167635 C25.619917,26.4902905 27.1478838,22.1191701 24.9507054,19.7888797 C19.8243983,24.3827386 17.0453112,25.8589212 5.91900415,22.8514523 C5.55485477,22.753195 5.67900415,22.1679668 6.06639004,22.020249 C8.16929461,21.2165975 12.933444,17.6965975 17.4406639,16.1450622 C18.2987552,15.8499585 19.1541909,15.6209129 19.9890456,15.4878008 C15.02639,15.0443154 12.7893776,14.3541909 9.63286307,14.8302075 C9.28697095,14.8823237 9.05195021,14.479668 9.26639004,14.2034855 C13.5193361,8.7253112 21.3540249,7.07087137 26.1878838,9.98107884 C23.2082988,6.28912863 20.8743568,3.34473029 20.8743568,3.34473029 L26.4046473,0.203485477 C26.4046473,0.203485477 28.6894606,5.30821577 30.3518672,9.02340249 C34.4657261,2.94506224 42.119834,2.38406639 45.3536929,6.69676349 C45.5455602,6.95302905 45.3450622,7.31751037 45.0247303,7.30987552 C42.3926971,7.24580913 40.9434025,9.63983402 40.833527,11.4605809 L40.8717012,11.4648963"
id="Fill-4"
fill="#00912D"
/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1,30 @@
import { mount, RouterLinkStub } from "@vue/test-utils";
import Film from "./";
import mockFilms from "@/test/fake-films.json";
describe("Film", () => {
it("tests props", () => {
expect(Film.props).toMatchObject({
film: {
type: Object,
default: {},
required: true
}
});
});
it("renders proper link to details", () => {
const wrapper = mount(Film, {
propsData: {
film: mockFilms[0]
},
stubs: {
RouterLink: RouterLinkStub
}
});
expect(wrapper.findComponent(RouterLinkStub).props().to).toBe(
`/films/${mockFilms[0].id}`
);
});
});

108
components/Film/index.vue Normal file
View File

@@ -0,0 +1,108 @@
<template>
<article
class="film flex flex-col justify-between bg-gray-100 prose prose-sm rounded-md shadow-md transition-shadow duration-300 hover:shadow-xl mb-4 sm:m-4"
>
<header class="p-4 rounded-t-md">
<h1>
{{ film.title + " (" + film.release_date + ")" }}
</h1>
<h2 class="text-gray-700 mt-0">{{ "by " + film.director }}</h2>
</header>
<main class="inline-block px-4">
<h3>Summary</h3>
<p>{{ excerptOfDescription }}</p>
</main>
<footer class="px-4 flex items-center justify-between">
<p
class="film-score font-bold text-lg"
:class="{
'text-green-600': film.rt_score >= 75,
'text-orange-600': film.rt_score >= 50 && film.rt_score < 75,
'text-yellow-600': film.rt_score >= 25 && film.rt_score < 50,
'text-red-600': film.rt_score >= 0 && film.rt_score < 25
}"
>
{{ film.rt_score + "%" }}
</p>
<router-link
class="rounded font-semibold bg-gray-400 py-2 px-4 border-b-4 border-gray-500"
:to="`/films/${film.id}`"
tag="button"
>
Read more
</router-link>
</footer>
</article>
</template>
<script>
export default {
name: "Film",
props: {
film: {
type: Object,
default: {},
required: true
}
},
computed: {
excerptOfDescription: function() {
return this.film.description.substring(0, 300) + "...";
}
}
};
</script>
<style lang="css" scoped>
@media screen and (min-width: 640px) {
.film {
margin: 1rem;
flex: 1 1 calc(50% - 2rem);
}
}
@media screen and (min-width: 1024px) {
.film {
flex: 1 1 calc(33% - 2rem);
}
}
header {
background-color: #85ffbd;
background-image: linear-gradient(45deg, #85ffbd 0%, #fffb7d 100%);
}
.film:nth-child(2n) header {
background-color: #ff9a8b;
background-image: linear-gradient(
90deg,
#ff9a8b 0%,
#ff6a88 55%,
#ff99ac 100%
);
}
.film:nth-child(4n) header {
background-color: #fee140;
background-image: linear-gradient(90deg, #fee140 0%, #fa709a 100%);
}
.film:nth-child(5n) header {
background-color: #a9c9ff;
background-image: linear-gradient(180deg, #a9c9ff 0%, #ffbbec 100%);
}
.film-score::before {
content: "";
background: url(~assets/svg/rotten-tomatoes.svg);
background-repeat: no-repeat;
background-size: 100%;
width: 1.5rem;
height: 1.5rem;
display: inline-block;
vertical-align: sub;
margin-right: 0.5rem;
}
</style>

View File

@@ -0,0 +1,9 @@
import { mount } from "@vue/test-utils";
import Footer from "./";
describe("Footer", () => {
it("should render Footer instance", () => {
const wrapper = mount(Footer);
expect(wrapper.exists()).toBe(true);
});
});

View File

@@ -0,0 +1,18 @@
<template>
<footer
class="bg-gray-900 text-gray-300 flex items-center justify-center h-16"
>
<p>
©2020 Made with <span style="color: red;">&#9829;</span> by
<a href="https://wazo-lab.io" target="_blank">Wazo Lab</a>
</p>
</footer>
</template>
<script>
export default {
name: "Footer"
};
</script>
<style></style>

View File

@@ -0,0 +1,26 @@
import { shallowMount } from "@vue/test-utils";
import Grid from "./";
import mockFilms from "@/test/fake-films.json";
describe("Grid", () => {
it("tests props", () => {
expect(Grid.props).toMatchObject({
dataSource: {
type: Array,
required: true
}
});
});
it("should render Grid instance with expected Film components", () => {
const wrapper = shallowMount(Grid, {
propsData: {
dataSource: mockFilms
}
});
expect(wrapper.findAllComponents({ name: "Film" }).length).toBe(
mockFilms.length
);
});
});

22
components/Grid/index.vue Normal file
View File

@@ -0,0 +1,22 @@
<template>
<div class="flex flex-wrap justify-center">
<Film :key="film.id" v-for="film in dataSource" :film="film" />
</div>
</template>
<script>
import Film from "../Film";
export default {
components: { Film },
name: "Grid",
props: {
dataSource: {
type: Array,
required: true
}
}
};
</script>
<style></style>

View File

@@ -0,0 +1,18 @@
import { mount, RouterLinkStub } from "@vue/test-utils";
import Header from "./";
describe("Header", () => {
it("should render Header instance", () => {
const wrapper = mount(Header, { stubs: ["router-link"] });
expect(wrapper.find("h1").text()).toBe("PeopleDoc");
});
it("should redirect to Home page when clicking on brand logo", () => {
const wrapper = mount(Header, {
stubs: {
RouterLink: RouterLinkStub
}
});
expect(wrapper.findComponent(RouterLinkStub).props().to).toBe("/");
});
});

View File

@@ -0,0 +1,31 @@
<template>
<header
id="header"
class="fixed top-0 left-0 w-screen bg-white h-16 px-4 md:px-8 py-3 shadow-md flex items-center justify-between"
>
<router-link to="/" class="flex w-auto h-full items-center">
<img
class="h-full mr-4"
src="https://avatars2.githubusercontent.com/u/1080062?s=200&v=4"
alt="PeopleDoc - Icon"
/>
<h1 class="text-2xl font-light tracking-wide">PeopleDoc</h1>
</router-link>
<h2 class="font-medium hidden sm:block">{{ description }}</h2>
</header>
</template>
<script>
import pkg from "@/package.json";
export default {
name: "Header",
computed: {
description: function() {
return pkg.description;
}
}
};
</script>
<style></style>

View File

@@ -0,0 +1,30 @@
import { mount, RouterLinkStub } from "@vue/test-utils";
import Person from "./";
import mockPeople from "@/test/fake-people.json";
describe("Person", () => {
it("tests props", () => {
expect(Person.props).toMatchObject({
person: {
type: Object,
default: {},
required: true
}
});
});
it("renders proper link to details", () => {
const wrapper = mount(Person, {
propsData: {
person: mockPeople[0]
},
stubs: {
RouterLink: RouterLinkStub
}
});
expect(wrapper.findComponent(RouterLinkStub).props().to).toBe(
`/people/${mockPeople[0].id}`
);
});
});

View File

@@ -0,0 +1,27 @@
<template>
<router-link
class="person flex items-center cursor-pointer p-3 mb-2 rounded bg-gray-300 hover:bg-gray-400"
:to="`/people/${person.id}`"
tag="article"
>
<span class="mr-2 text-xl font-bold">{{
person.gender === "Female" ? "&#9792;" : "&#9794;"
}}</span>
<h4>{{ person.name }}</h4>
</router-link>
</template>
<script>
export default {
name: "Person",
props: {
person: {
type: Object,
default: {},
required: true
}
}
};
</script>
<style lang="css" scoped></style>

7
components/README.md Normal file
View File

@@ -0,0 +1,7 @@
# COMPONENTS
**This directory is not required, you can delete it if you don't want to use it.**
The components directory contains your Vue.js Components.
_Nuxt.js doesn't supercharge these components._

18
jest.config.js Normal file
View File

@@ -0,0 +1,18 @@
module.exports = {
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/$1",
"^~/(.*)$": "<rootDir>/$1",
"^vue$": "vue/dist/vue.common.js"
},
moduleFileExtensions: ["js", "vue", "json"],
transform: {
"^.+\\.js$": "babel-jest",
".*\\.(vue)$": "vue-jest"
},
coverageDirectory: "<rootDir>/coverage",
collectCoverage: true,
collectCoverageFrom: [
"<rootDir>/components/**/*.vue",
"<rootDir>/pages/**/*.vue"
]
};

11
jsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "es2015",
"module": "esnext",
"baseUrl": "./",
"paths": {
"@/*": ["components/*"]
}
},
"include": ["./**/*.vue", "./**/*.js"]
}

7
layouts/README.md Normal file
View File

@@ -0,0 +1,7 @@
# LAYOUTS
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your Application Layouts.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts).

45
layouts/default.vue Normal file
View File

@@ -0,0 +1,45 @@
<template>
<div>
<Header />
<main class="mt-16 px-4">
<Nuxt />
</main>
<Footer />
</div>
</template>
<script>
export default {
middleware: ["redirect"],
fetchOnServer: false,
async fetch() {
const { store } = this.$nuxt.context;
if (!store.state.people.list.length) await store.dispatch("people/getList");
if (!store.state.vehicles.list.length)
await store.dispatch("vehicles/getList");
if (!store.state.locations.list.length)
await store.dispatch("locations/getList");
}
};
</script>
<style>
html {
font-family: "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 16px;
word-spacing: 1px;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
box-sizing: border-box;
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
}
</style>

8
middleware/README.md Normal file
View File

@@ -0,0 +1,8 @@
# MIDDLEWARE
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your application middleware.
Middleware let you define custom functions that can be run before rendering either a page or a group of pages.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware).

3
middleware/redirect.js Normal file
View File

@@ -0,0 +1,3 @@
export default function({ redirect, route }) {
if (route.path === "/") redirect("/films");
}

54
nuxt.config.js Normal file
View File

@@ -0,0 +1,54 @@
export default {
// Disable server-side rendering (https://go.nuxtjs.dev/ssr-mode)
ssr: false,
// Target (https://go.nuxtjs.dev/config-target)
target: "static",
// Global page headers (https://go.nuxtjs.dev/config-head)
head: {
title: "Ghibli",
meta: [
{ charset: "utf-8" },
{ name: "viewport", content: "width=device-width, initial-scale=1" },
{
hid: "description",
name: "description",
content: "A test project for PeopleDoc."
}
],
link: [{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" }]
},
// Global CSS (https://go.nuxtjs.dev/config-css)
css: [],
// Plugins to run before rendering page (https://go.nuxtjs.dev/config-plugins)
plugins: [],
// Auto import components (https://go.nuxtjs.dev/config-components)
components: true,
// Modules for dev and build (recommended) (https://go.nuxtjs.dev/config-modules)
buildModules: [
// https://go.nuxtjs.dev/tailwindcss
"@nuxtjs/tailwindcss"
],
// Modules (https://go.nuxtjs.dev/config-modules)
modules: ["@nuxtjs/axios"],
axios: {
proxy: true
},
proxy: {
"/api/": {
target: "https://ghibliapi.herokuapp.com",
pathRewrite: { "^/api/": "" }
}
},
// Build Configuration (https://go.nuxtjs.dev/config-build)
build: {}
};

15175
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

27
package.json Normal file
View File

@@ -0,0 +1,27 @@
{
"name": "ghibli-api",
"description": "A test project for PeopleDoc.",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
"test": "jest"
},
"dependencies": {
"@nuxtjs/axios": "^5.12.4",
"@tailwindcss/typography": "^0.3.1",
"core-js": "^3.6.5",
"nuxt": "^2.14.6"
},
"devDependencies": {
"@nuxtjs/tailwindcss": "^3.1.0",
"@vue/test-utils": "^1.1.0",
"babel-core": "7.0.0-bridge.0",
"babel-jest": "^26.5.0",
"jest": "^26.5.0",
"vue-jest": "^3.0.4"
}
}

6
pages/README.md Normal file
View File

@@ -0,0 +1,6 @@
# PAGES
This directory contains your Application Views and Routes.
The framework reads all the `*.vue` files inside this directory and creates the router of your application.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing).

77
pages/films/_id.spec.js Normal file
View File

@@ -0,0 +1,77 @@
import { mount, createLocalVue } from "@vue/test-utils";
import Vuex from "vuex";
import Film from "./_id";
import mockFilms from "@/test/fake-films.json";
let $route = {
path: "/films",
params: {
id: "2baf70d1-42bb-4437-b551-e5fed5a87abe"
}
};
const localVue = createLocalVue();
localVue.use(Vuex);
describe("Film page", () => {
let state, actions, store;
beforeEach(() => {
state = {
film: {}
};
actions = {
getFilm: jest.fn()
};
store = new Vuex.Store({
modules: {
films: {
namespaced: true,
state,
actions
}
}
});
});
it("should render Film page instance", () => {
const wrapper = mount(Film, {
localVue,
store,
computed: {
film: () => mockFilms[0],
people: () => jest.fn()
}
});
expect(wrapper.exists()).toBe(true);
});
it("tests params validation", () => {
expect(Film.validate({ params: $route.params })).toBe(true);
expect(
Film.validate({
params: { id: "2baf70d1-42bb-4437-b551-e5fed5a87abe-1234" }
})
).toBe(false);
expect(
Film.validate({
params: { id: "2baf7e0d1-42bb-4437-b551-e5fed5a87abe" }
})
).toBe(false);
});
it("should dispatch getFilm action", async () => {
let wrapper = mount(Film, {
localVue,
store,
computed: {
film: () => mockFilms[0],
people: () => jest.fn()
}
});
await wrapper.vm.$options.asyncData({ store, params: $route.params });
expect(actions.getFilm).toHaveBeenCalled();
});
});

113
pages/films/_id.vue Normal file
View File

@@ -0,0 +1,113 @@
<template>
<div
class="py-4 flex flex-col lg:flex-row items-center lg:items-start justify-center"
>
<article
class="mb-4 inline-flex flex-col justify-between bg-gray-100 prose prose-sm rounded-md shadow-md transition-shadow duration-300 hover:shadow-xl"
>
<header class="p-4 rounded-t-md">
<h1>
{{ film.title + " (" + film.release_date + ")" }}
</h1>
<h2 class="text-gray-700 mt-0">{{ "by " + film.director }}</h2>
</header>
<section class="px-4 infos">
<p>{{ film.description }}</p>
<p
class="film-score font-bold text-lg"
:class="{
'text-green-600': film.rt_score >= 75,
'text-orange-600': film.rt_score >= 50 && film.rt_score < 75,
'text-yellow-600': film.rt_score >= 25 && film.rt_score < 50,
'text-red-600': film.rt_score >= 0 && film.rt_score < 25
}"
>
{{ film.rt_score + "%" }}
</p>
</section>
</article>
<aside class="lg:inline-flex lg:ml-4">
<section
class="p-4 lg:mr-4 bg-gray-100 rounded-md shadow-md transition-shadow duration-300 hover:shadow-xl"
>
<h3 class="font-medium text-lg mb-4">People</h3>
<ul v-if="people.length">
<li :key="person.id" v-for="person in people">
<Person :person="person" />
</li>
</ul>
</section>
<!-- <section
v-if="Object.keys(film.vehicles).length"
class="p-4 lg:mr-4 bg-gray-100 rounded-md shadow-md transition-shadow duration-300 hover:shadow-xl"
>
<h3 class="font-medium text-lg mb-4">Vehicles</h3>
<div>{{ film.vehicles }}</div>
</section>
<section
v-if="Object.keys(film.locations).length"
class="p-4 lg:mr-4 bg-gray-100 rounded-md shadow-md transition-shadow duration-300 hover:shadow-xl"
>
<h3 class="font-medium text-lg">Locations</h3>
<div>{{ film.locations }}</div>
</section> -->
</aside>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "Film",
components: {
Person: () => import("@/components/Person")
},
head() {
return {
titleTemplate: `%s - ${this.film.title}`
};
},
validate({ params }) {
const uuid = new RegExp(
/^[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-4[0-9A-Za-z]{3}-[89ABab][0-9A-Za-z]{3}-[0-9A-Za-z]{12}$/
);
return uuid.test(params.id);
},
async asyncData({ params, store }) {
if (!store.state.films.film.id !== params.id)
await store.dispatch("films/getFilm", params.id);
},
computed: {
people() {
return this.$store.getters["people/getPeopleByFilmId"](this.film.id);
},
...mapGetters({
film: "films/film"
})
}
};
</script>
<style lang="css">
article header {
background-color: #85ffbd;
background-image: linear-gradient(45deg, #85ffbd 0%, #fffb7d 100%);
}
.film-score::before {
content: "";
background: url(~assets/svg/rotten-tomatoes.svg);
background-repeat: no-repeat;
background-size: 100%;
width: 1.5rem;
height: 1.5rem;
display: inline-block;
vertical-align: sub;
margin-right: 0.5rem;
}
</style>

48
pages/films/index.spec.js Normal file
View File

@@ -0,0 +1,48 @@
import { shallowMount, createLocalVue } from "@vue/test-utils";
import Vuex from "vuex";
import Films from "./";
const localVue = createLocalVue();
localVue.use(Vuex);
describe("Films page", () => {
let state, actions, getters, store;
beforeEach(() => {
state = {
list: []
};
actions = {
getList: jest.fn()
};
getters = {
list: jest.fn()
};
store = new Vuex.Store({
modules: {
films: {
namespaced: true,
state,
actions,
getters
}
}
});
});
it("should render Films page instance", () => {
const wrapper = shallowMount(Films, { store, localVue });
expect(wrapper.find("img").exists()).toBe(true);
});
it("should dispatch films/getList action", async () => {
let wrapper = shallowMount(Films, {
store,
localVue
});
await wrapper.vm.$options.asyncData({ store });
expect(actions.getList).toHaveBeenCalled();
expect(wrapper.findComponent({ name: "Grid" }).exists()).toBe(true);
});
});

33
pages/films/index.vue Normal file
View File

@@ -0,0 +1,33 @@
<template>
<section class="py-4">
<img
class="w-full max-w-md mx-auto mb-8"
src="~/assets/images/logo.png"
alt="Studio Ghibli"
/>
<Grid :dataSource="films" />
</section>
</template>
<script>
import Grid from "@/components/Grid";
import { mapGetters } from "vuex";
export default {
name: "Films",
components: { Grid },
head: {
titleTemplate: "%s - Films"
},
async asyncData({ store }) {
if (!store.state.films.list.length) await store.dispatch("films/getList");
},
computed: {
...mapGetters({
films: "films/list"
})
}
};
</script>
<style></style>

View File

@@ -0,0 +1,56 @@
import { mount, createLocalVue } from "@vue/test-utils";
import Vuex from "vuex";
import Location from "./_id";
let $route = {
path: "/locations",
params: {
id: "2baf70d1-42bb-4437-b551-e5fed5a87abe"
}
};
const localVue = createLocalVue();
localVue.use(Vuex);
describe("Location page", () => {
let state, actions, store;
beforeEach(() => {
state = {
location: {}
};
actions = {
getLocation: jest.fn()
};
store = new Vuex.Store({ state, actions });
});
it("should render Location page instance", () => {
const wrapper = mount(Location, { localVue, store });
expect(wrapper.exists()).toBe(true);
});
it("tests params validation", () => {
expect(Location.validate({ params: $route.params })).toBe(true);
expect(
Location.validate({
params: { id: "2baf70d1-42bb-4437-b551-e5fed5a87abe-1234" }
})
).toBe(false);
expect(
Location.validate({
params: { id: "2baf7e0d1-42bb-4437-b551-e5fed5a87abe" }
})
).toBe(false);
});
it("should dispatch getLocation action", async () => {
let wrapper = mount(Location, {
localVue,
store
});
await wrapper.vm.$options.asyncData({ store, params: $route.params });
expect(actions.getLocation).toHaveBeenCalled();
});
});

32
pages/locations/_id.vue Normal file
View File

@@ -0,0 +1,32 @@
<template>
<div>
{{ location }}
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "Location",
head() {
return {
titleTemplate: `%s - ${this.location.name}`
};
},
validate({ params }) {
const uuid = new RegExp(
/^[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-4[0-9A-Za-z]{3}-[89ABab][0-9A-Za-z]{3}-[0-9A-Za-z]{12}$/
);
return uuid.test(params.id);
},
async asyncData({ params, store }) {
await store.dispatch("getLocation", params.id);
},
computed: {
...mapState(["location"])
}
};
</script>
<style></style>

56
pages/people/_id.spec.js Normal file
View File

@@ -0,0 +1,56 @@
import { mount, createLocalVue } from "@vue/test-utils";
import Vuex from "vuex";
import Person from "./_id";
let $route = {
path: "/people",
params: {
id: "2baf70d1-42bb-4437-b551-e5fed5a87abe"
}
};
const localVue = createLocalVue();
localVue.use(Vuex);
describe("Person page", () => {
let state, actions, store;
beforeEach(() => {
state = {
person: {}
};
actions = {
getPerson: jest.fn()
};
store = new Vuex.Store({ state, actions });
});
it("should render Person page instance", () => {
const wrapper = mount(Person, { localVue, store });
expect(wrapper.exists()).toBe(true);
});
it("tests params validation", () => {
expect(Person.validate({ params: $route.params })).toBe(true);
expect(
Person.validate({
params: { id: "2baf70d1-42bb-4437-b551-e5fed5a87abe-1234" }
})
).toBe(false);
expect(
Person.validate({
params: { id: "2baf7e0d1-42bb-4437-b551-e5fed5a87abe" }
})
).toBe(false);
});
it("should dispatch getPerson action", async () => {
let wrapper = mount(Person, {
localVue,
store
});
await wrapper.vm.$options.asyncData({ store, params: $route.params });
expect(actions.getPerson).toHaveBeenCalled();
});
});

32
pages/people/_id.vue Normal file
View File

@@ -0,0 +1,32 @@
<template>
<div>
{{ person }}
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "Person",
head() {
return {
titleTemplate: `%s - ${this.person.name}`
};
},
validate({ params }) {
const uuid = new RegExp(
/^[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-4[0-9A-Za-z]{3}-[89ABab][0-9A-Za-z]{3}-[0-9A-Za-z]{12}$/
);
return uuid.test(params.id);
},
async asyncData({ params, store }) {
await store.dispatch("getPerson", params.id);
},
computed: {
...mapState(["person"])
}
};
</script>
<style></style>

View File

@@ -0,0 +1,56 @@
import { mount, createLocalVue } from "@vue/test-utils";
import Vuex from "vuex";
import Vehicle from "./_id";
let $route = {
path: "/vehicles",
params: {
id: "2baf70d1-42bb-4437-b551-e5fed5a87abe"
}
};
const localVue = createLocalVue();
localVue.use(Vuex);
describe("Vehicle page", () => {
let state, actions, store;
beforeEach(() => {
state = {
vehicle: {}
};
actions = {
getVehicle: jest.fn()
};
store = new Vuex.Store({ state, actions });
});
it("should render Vehicle page instance", () => {
const wrapper = mount(Vehicle, { localVue, store });
expect(wrapper.exists()).toBe(true);
});
it("tests params validation", () => {
expect(Vehicle.validate({ params: $route.params })).toBe(true);
expect(
Vehicle.validate({
params: { id: "2baf70d1-42bb-4437-b551-e5fed5a87abe-1234" }
})
).toBe(false);
expect(
Vehicle.validate({
params: { id: "2baf7e0d1-42bb-4437-b551-e5fed5a87abe" }
})
).toBe(false);
});
it("should dispatch getVehicle action", async () => {
let wrapper = mount(Vehicle, {
localVue,
store
});
await wrapper.vm.$options.asyncData({ store, params: $route.params });
expect(actions.getVehicle).toHaveBeenCalled();
});
});

32
pages/vehicles/_id.vue Normal file
View File

@@ -0,0 +1,32 @@
<template>
<div>
{{ vehicle }}
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "Vehicle",
head() {
return {
titleTemplate: `%s - ${this.vehicle.name}`
};
},
validate({ params }) {
const uuid = new RegExp(
/^[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-4[0-9A-Za-z]{3}-[89ABab][0-9A-Za-z]{3}-[0-9A-Za-z]{12}$/
);
return uuid.test(params.id);
},
async asyncData({ params, store }) {
await store.dispatch("getVehicle", params.id);
},
computed: {
...mapState(["vehicle"])
}
};
</script>
<style></style>

7
plugins/README.md Normal file
View File

@@ -0,0 +1,7 @@
# PLUGINS
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains Javascript plugins that you want to run before mounting the root Vue.js application.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins).

11
static/README.md Normal file
View File

@@ -0,0 +1,11 @@
# STATIC
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your static files.
Each file inside this directory is mapped to `/`.
Thus you'd want to delete this README.md before deploying to production.
Example: `/static/robots.txt` is mapped as `/robots.txt`.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static).

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

10
store/README.md Normal file
View File

@@ -0,0 +1,10 @@
# STORE
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your Vuex Store files.
Vuex Store option is implemented in the Nuxt.js framework.
Creating a file in this directory automatically activates the option in the framework.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store).

View File

@@ -0,0 +1,37 @@
import { actions } from "./";
import axios from "axios";
import mockFilms from "@/test/fake-films.json";
let url = "";
jest.mock("axios", () => ({
$get: _url => {
return new Promise(resolve => {
url = _url;
resolve(mockFilms);
});
}
}));
describe("Vuex actions.", () => {
it("tests films/getList action.", async () => {
const commit = jest.fn();
actions.$axios = axios;
await actions.getList({ commit });
expect(url).toBe(
"/api/films?fields=id,title,release_date,director,description,rt_score"
);
expect(commit).toHaveBeenCalledWith("setList", mockFilms);
});
it("catches errors.", async () => {
const commit = jest.fn();
actions.$axios = null;
await expect(actions.getList({ commit })).rejects.toThrow(
"API Error occurred: Cannot read property '$get' of null"
);
});
});

41
store/films/index.js Normal file
View File

@@ -0,0 +1,41 @@
export const state = () => ({
list: [],
film: {}
});
export const mutations = {
setList: (state, films) => {
state.list = films;
},
setFilm: (state, film) => {
state.film = film;
}
};
export const actions = {
async getList({ commit }) {
try {
const films = await this.$axios.$get(
"/api/films?fields=id,title,release_date,director,description,rt_score"
);
commit("setList", films);
} catch (e) {
throw Error(`API Error occurred: ${e.message}`);
}
},
async getFilm({ commit }, id) {
try {
const film = await this.$axios.$get(
`/api/films/${id}?fields=id,title,release_date,director,description,rt_score`
);
commit("setFilm", film);
} catch (e) {
throw Error(`API Error occurred: ${e.message}`);
}
}
};
export const getters = {
list: state => state.list,
film: state => state.film
};

View File

@@ -0,0 +1,9 @@
import { state, mutations } from "./";
import mockFilms from "@/test/fake-films.json";
describe("Vuex mutations.", () => {
it("tests setFilms mutation.", () => {
mutations.setList(state, mockFilms);
expect(state.list[0].id).toBe(mockFilms[0].id);
});
});

7
store/index.js Normal file
View File

@@ -0,0 +1,7 @@
export const state = () => ({});
export const mutations = {};
export const actions = {};
export const getters = {};

39
store/locations/index.js Normal file
View File

@@ -0,0 +1,39 @@
export const state = () => ({
list: [],
location: {}
});
export const mutations = {
setList: (state, locations) => {
state.list = locations;
},
setLocation: (state, location) => {
state.location = location;
}
};
export const actions = {
async getList({ commit }) {
try {
const locations = await this.$axios.$get(
"/api/locations?fields=id,name,climate,terrain,surface_water,residents,films"
);
commit("setList", locations);
} catch (e) {
throw Error(`API Error occurred: ${e.message}`);
}
},
async getLocation({ commit }, id) {
try {
const location = await this.$axios.$get(`/api/locations/${id}`);
commit("setLocation", location);
} catch (e) {
throw Error(`API Error occurred: ${e.message}`);
}
}
};
export const getters = {
list: state => state.list,
location: state => state.location
};

44
store/people/index.js Normal file
View File

@@ -0,0 +1,44 @@
export const state = () => ({
list: [],
person: {}
});
export const mutations = {
setList: (state, people) => {
state.list = people;
},
setPerson: (state, person) => {
state.person = person;
}
};
export const actions = {
async getList({ commit }) {
try {
const people = await this.$axios.$get(
"/api/people?fields=id,name,gender,age,eye_color,hair_color,films"
);
commit("setList", people);
} catch (e) {
throw Error(`API Error occurred: ${e.message}`);
}
},
async getPerson({ commit }, id) {
try {
const person = await this.$axios.$get(`/api/people/${id}`);
commit("setPerson", person);
} catch (e) {
throw Error(`API Error occurred: ${e.message}`);
}
}
};
export const getters = {
list: state => state.list,
person: state => state.person,
getPeopleByFilmId: state => id => {
return state.list.filter(person =>
person.films.find(film => film.split("/")[4] === id)
);
}
};

39
store/vehicles/index.js Normal file
View File

@@ -0,0 +1,39 @@
export const state = () => ({
list: [],
vehicle: {}
});
export const mutations = {
setList: (state, vehicles) => {
state.list = vehicles;
},
setVehicle: (state, vehicle) => {
state.vehicle = vehicle;
}
};
export const actions = {
async getList({ commit }) {
try {
const vehicles = await this.$axios.$get(
"/api/vehicles?fields=id,name,description,vehicle_class,length,pilot,films"
);
commit("setList", vehicles);
} catch (e) {
throw Error(`API Error occurred: ${e.message}`);
}
},
async getVehicle({ commit }, id) {
try {
const vehicle = await this.$axios.$get(`/api/vehicles/${id}`);
commit("setVehicle", vehicle);
} catch (e) {
throw Error(`API Error occurred: ${e.message}`);
}
}
};
export const getters = {
list: state => state.list,
vehicle: state => state.vehicle
};

20
tailwind.config.js Normal file
View File

@@ -0,0 +1,20 @@
module.exports = {
purge: {
enabled: process.env.NODE_ENV === "production",
content: [
"./components/**/*.{vue,js}",
"./layouts/**/*.vue",
"./pages/**/*.vue",
"./plugins/**/*.{js,ts}",
"./nuxt.config.{js,ts}"
]
},
darkMode: false,
theme: {
extend: {}
},
variants: {
extend: {}
},
plugins: [require("@tailwindcss/typography")]
};

464
test/fake-films.json Normal file
View File

@@ -0,0 +1,464 @@
[
{
"id": "2baf70d1-42bb-4437-b551-e5fed5a87abe",
"title": "Castle in the Sky",
"description": "The orphan Sheeta inherited a mysterious crystal that links her to the mythical sky-kingdom of Laputa. With the help of resourceful Pazu and a rollicking band of sky pirates, she makes her way to the ruins of the once-great civilization. Sheeta and Pazu must outwit the evil Muska, who plans to use Laputa's science to make himself ruler of the world.",
"director": "Hayao Miyazaki",
"producer": "Isao Takahata",
"release_date": "1986",
"rt_score": "95",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
},
{
"id": "12cfb892-aac0-4c5b-94af-521852e46d6a",
"title": "Grave of the Fireflies",
"description": "In the latter part of World War II, a boy and his sister, orphaned when their mother is killed in the firebombing of Tokyo, are left to survive on their own in what remains of civilian life in Japan. The plot follows this boy and his sister as they do their best to survive in the Japanese countryside, battling hunger, prejudice, and pride in their own quiet, personal battle.",
"director": "Isao Takahata",
"producer": "Toru Hara",
"release_date": "1988",
"rt_score": "97",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/12cfb892-aac0-4c5b-94af-521852e46d6a"
},
{
"id": "58611129-2dbc-4a81-a72f-77ddfc1b1b49",
"title": "My Neighbor Totoro",
"description": "Two sisters move to the country with their father in order to be closer to their hospitalized mother, and discover the surrounding trees are inhabited by Totoros, magical spirits of the forest. When the youngest runs away from home, the older sister seeks help from the spirits to find her.",
"director": "Hayao Miyazaki",
"producer": "Hayao Miyazaki",
"release_date": "1988",
"rt_score": "93",
"people": [
"https://ghibliapi.herokuapp.com/people/986faac6-67e3-4fb8-a9ee-bad077c2e7fe",
"https://ghibliapi.herokuapp.com/people/d5df3c04-f355-4038-833c-83bd3502b6b9",
"https://ghibliapi.herokuapp.com/people/3031caa8-eb1a-41c6-ab93-dd091b541e11",
"https://ghibliapi.herokuapp.com/people/87b68b97-3774-495b-bf80-495a5f3e672d",
"https://ghibliapi.herokuapp.com/people/d39deecb-2bd0-4770-8b45-485f26e1381f",
"https://ghibliapi.herokuapp.com/people/591524bc-04fe-4e60-8d61-2425e42ffb2a",
"https://ghibliapi.herokuapp.com/people/c491755a-407d-4d6e-b58a-240ec78b5061",
"https://ghibliapi.herokuapp.com/people/f467e18e-3694-409f-bdb3-be891ade1106",
"https://ghibliapi.herokuapp.com/people/08ffbce4-7f94-476a-95bc-76d3c3969c19",
"https://ghibliapi.herokuapp.com/people/0f8ef701-b4c7-4f15-bd15-368c7fe38d0a"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
"https://ghibliapi.herokuapp.com/species/74b7f547-1577-4430-806c-c358c8b6bcf5"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
},
{
"id": "ea660b10-85c4-4ae3-8a5f-41cea3648e3e",
"title": "Kiki's Delivery Service",
"description": "A young witch, on her mandatory year of independent life, finds fitting into a new community difficult while she supports herself by running an air courier service.",
"director": "Hayao Miyazaki",
"producer": "Hayao Miyazaki",
"release_date": "1989",
"rt_score": "96",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/ea660b10-85c4-4ae3-8a5f-41cea3648e3e"
},
{
"id": "4e236f34-b981-41c3-8c65-f8c9000b94e7",
"title": "Only Yesterday",
"description": "Its 1982, and Taeko is 27 years old, unmarried, and has lived her whole life in Tokyo. She decides to visit her family in the countryside, and as the train travels through the night, memories flood back of her younger years: the first immature stirrings of romance, the onset of puberty, and the frustrations of math and boys. At the station she is met by young farmer Toshio, and the encounters with him begin to reconnect her to forgotten longings. In lyrical switches between the present and the past, Taeko contemplates the arc of her life, and wonders if she has been true to the dreams of her childhood self.",
"director": "Isao Takahata",
"producer": "Toshio Suzuki",
"release_date": "1991",
"rt_score": "100",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/4e236f34-b981-41c3-8c65-f8c9000b94e7"
},
{
"id": "ebbb6b7c-945c-41ee-a792-de0e43191bd8",
"title": "Porco Rosso",
"description": "Porco Rosso, known in Japan as Crimson Pig (Kurenai no Buta) is the sixth animated film by Hayao Miyazaki and released in 1992. You're introduced to an Italian World War I fighter ace, now living as a freelance bounty hunter chasing 'air pirates' in the Adriatic Sea. He has been given a curse that changed his head to that of a pig. Once called Marco Pagot, he is now known to the world as 'Porco Rosso', Italian for 'Red Pig.'",
"director": "Hayao Miyazaki",
"producer": "Toshio Suzuki",
"release_date": "1992",
"rt_score": "94",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/ebbb6b7c-945c-41ee-a792-de0e43191bd8"
},
{
"id": "1b67aa9a-2e4a-45af-ac98-64d6ad15b16c",
"title": "Pom Poko",
"description": "As the human city development encroaches on the raccoon population's forest and meadow habitat, the raccoons find themselves faced with the very real possibility of extinction. In response, the raccoons engage in a desperate struggle to stop the construction and preserve their home.",
"director": "Isao Takahata",
"producer": "Toshio Suzuki",
"release_date": "1994",
"rt_score": "78",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/1b67aa9a-2e4a-45af-ac98-64d6ad15b16c"
},
{
"id": "ff24da26-a969-4f0e-ba1e-a122ead6c6e3",
"title": "Whisper of the Heart",
"description": "Shizuku lives a simple life, dominated by her love for stories and writing. One day she notices that all the library books she has have been previously checked out by the same person: 'Seiji Amasawa'. Curious as to who he is, Shizuku meets a boy her age whom she finds infuriating, but discovers to her shock that he is her 'Prince of Books'. As she grows closer to him, she realises that he merely read all those books to bring himself closer to her. The boy Seiji aspires to be a violin maker in Italy, and it is his dreams that make Shizuku realise that she has no clear path for her life. Knowing that her strength lies in writing, she tests her talents by writing a story about Baron, a cat statuette belonging to Seiji's grandfather.",
"director": "Yoshifumi Kondō",
"producer": "Toshio Suzuki",
"release_date": "1995",
"rt_score": "91",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/ff24da26-a969-4f0e-ba1e-a122ead6c6e3"
},
{
"id": "0440483e-ca0e-4120-8c50-4c8cd9b965d6",
"title": "Princess Mononoke",
"description": "Ashitaka, a prince of the disappearing Ainu tribe, is cursed by a demonized boar god and must journey to the west to find a cure. Along the way, he encounters San, a young human woman fighting to protect the forest, and Lady Eboshi, who is trying to destroy it. Ashitaka must find a way to bring balance to this conflict.",
"director": "Hayao Miyazaki",
"producer": "Toshio Suzuki",
"release_date": "1997",
"rt_score": "92",
"people": [
"https://ghibliapi.herokuapp.com/people/ba924631-068e-4436-b6de-f3283fa848f0",
"https://ghibliapi.herokuapp.com/people/ebe40383-aad2-4208-90ab-698f00c581ab",
"https://ghibliapi.herokuapp.com/people/030555b3-4c92-4fce-93fb-e70c3ae3df8b",
"https://ghibliapi.herokuapp.com/people/ca568e87-4ce2-4afa-a6c5-51f4ae80a60b",
"https://ghibliapi.herokuapp.com/people/e9356bb5-4d4a-4c93-aadc-c83e514bffe3",
"https://ghibliapi.herokuapp.com/people/34277bec-7401-43fa-a00a-5aee64b45b08",
"https://ghibliapi.herokuapp.com/people/91939012-90b9-46e5-a649-96b898073c82",
"https://ghibliapi.herokuapp.com/people/20e3bd33-b35d-41e6-83a4-57ca7f028d38",
"https://ghibliapi.herokuapp.com/people/8bccdc78-545b-49f4-a4c8-756163a38c91",
"https://ghibliapi.herokuapp.com/people/116bfe1b-3ba8-4fa0-8f72-88537a493cb9"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"https://ghibliapi.herokuapp.com/species/6bc92fdd-b0f4-4286-ad71-1f99fb4a0d1e",
"https://ghibliapi.herokuapp.com/species/f25fa661-3073-414d-968a-ab062e3065f7"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
},
{
"id": "45204234-adfd-45cb-a505-a8e7a676b114",
"title": "My Neighbors the Yamadas",
"description": "The Yamadas are a typical middle class Japanese family in urban Tokyo and this film shows us a variety of episodes of their lives. With tales that range from the humourous to the heartbreaking, we see this family cope with life's little conflicts, problems and joys in their own way.",
"director": "Isao Takahata",
"producer": "Toshio Suzuki",
"release_date": "1999",
"rt_score": "75",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/45204234-adfd-45cb-a505-a8e7a676b114"
},
{
"id": "dc2e6bd1-8156-4886-adff-b39e6043af0c",
"title": "Spirited Away",
"description": "Spirited Away is an Oscar winning Japanese animated film about a ten year old girl who wanders away from her parents along a path that leads to a world ruled by strange and unusual monster-like animals. Her parents have been changed into pigs along with others inside a bathhouse full of these creatures. Will she ever see the world how it once was?",
"director": "Hayao Miyazaki",
"producer": "Toshio Suzuki",
"release_date": "2001",
"rt_score": "97",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/dc2e6bd1-8156-4886-adff-b39e6043af0c"
},
{
"id": "90b72513-afd4-4570-84de-a56c312fdf81",
"title": "The Cat Returns",
"description": "Haru, a schoolgirl bored by her ordinary routine, saves the life of an unusual cat and suddenly her world is transformed beyond anything she ever imagined. The Cat King rewards her good deed with a flurry of presents, including a very shocking proposal of marriage to his son! Haru embarks on an unexpected journey to the Kingdom of Cats where her eyes are opened to a whole other world.",
"director": "Hiroyuki Morita",
"producer": "Toshio Suzuki",
"release_date": "2002",
"rt_score": "89",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/90b72513-afd4-4570-84de-a56c312fdf81"
},
{
"id": "cd3d059c-09f4-4ff3-8d63-bc765a5184fa",
"title": "Howl's Moving Castle",
"description": "When Sophie, a shy young woman, is cursed with an old body by a spiteful witch, her only chance of breaking the spell lies with a self-indulgent yet insecure young wizard and his companions in his legged, walking home.",
"director": "Hayao Miyazaki",
"producer": "Toshio Suzuki",
"release_date": "2004",
"rt_score": "87",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/cd3d059c-09f4-4ff3-8d63-bc765a5184fa"
},
{
"id": "112c1e67-726f-40b1-ac17-6974127bb9b9",
"title": "Tales from Earthsea",
"description": "Something bizarre has come over the land. The kingdom is deteriorating. People are beginning to act strange... What's even more strange is that people are beginning to see dragons, which shouldn't enter the world of humans. Due to all these bizarre events, Ged, a wandering wizard, is investigating the cause. During his journey, he meets Prince Arren, a young distraught teenage boy. While Arren may look like a shy young teen, he has a severe dark side, which grants him strength, hatred, ruthlessness and has no mercy, especially when it comes to protecting Teru. For the witch Kumo this is a perfect opportunity. She can use the boy's 'fears' against the very one who would help him, Ged.",
"director": "Gorō Miyazaki",
"producer": "Toshio Suzuki",
"release_date": "2006",
"rt_score": "41",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/112c1e67-726f-40b1-ac17-6974127bb9b9"
},
{
"id": "758bf02e-3122-46e0-884e-67cf83df1786",
"title": "Ponyo",
"description": "The son of a sailor, 5-year old Sosuke lives a quiet life on an oceanside cliff with his mother Lisa. One fateful day, he finds a beautiful goldfish trapped in a bottle on the beach and upon rescuing her, names her Ponyo. But she is no ordinary goldfish. The daughter of a masterful wizard and a sea goddess, Ponyo uses her father's magic to transform herself into a young girl and quickly falls in love with Sosuke, but the use of such powerful sorcery causes a dangerous imbalance in the world. As the moon steadily draws nearer to the earth and Ponyo's father sends the ocean's mighty waves to find his daughter, the two children embark on an adventure of a lifetime to save the world and fulfill Ponyo's dreams of becoming human.",
"director": "Hayao Miyazaki",
"producer": "Toshio Suzuki",
"release_date": "2008",
"rt_score": "92",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/758bf02e-3122-46e0-884e-67cf83df1786"
},
{
"id": "2de9426b-914a-4a06-a3a0-5e6d9d3886f6",
"title": "Arrietty",
"description": "14-year-old Arrietty and the rest of the Clock family live in peaceful anonymity as they make their own home from items 'borrowed' from the house's human inhabitants. However, life changes for the Clocks when a human boy discovers Arrietty.",
"director": "Hiromasa Yonebayashi",
"producer": "Toshio Suzuki",
"release_date": "2010",
"rt_score": "95",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/2de9426b-914a-4a06-a3a0-5e6d9d3886f6"
},
{
"id": "45db04e4-304a-4933-9823-33f389e8d74d",
"title": "From Up on Poppy Hill",
"description": "The story is set in 1963 in Yokohama. Kokuriko Manor sits on a hill overlooking the harbour. A 16 year-old girl, Umi, lives in that house. Every morning she raises a signal flag facing the sea. The flag means “I pray for safe voyages”. A 17 year-old boy, Shun, always sees this flag from the sea as he rides a tugboat to school. Gradually the pair are drawn to each other but they are faced with a sudden trial. Even so, they keep going without running from facing the hardships of reality.",
"director": "Gorō Miyazaki",
"producer": "Toshio Suzuki",
"release_date": "2011",
"rt_score": "83",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/45db04e4-304a-4933-9823-33f389e8d74d"
},
{
"id": "67405111-37a5-438f-81cc-4666af60c800",
"title": "The Wind Rises",
"description": "A lifelong love of flight inspires Japanese aviation engineer Jiro Horikoshi, whose storied career includes the creation of the A-6M World War II fighter plane.",
"director": "Hayao Miyazaki",
"producer": "Toshio Suzuki",
"release_date": "2013",
"rt_score": "89",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/67405111-37a5-438f-81cc-4666af60c800"
},
{
"id": "578ae244-7750-4d9f-867b-f3cd3d6fecf4",
"title": "The Tale of the Princess Kaguya",
"description": "A bamboo cutter named Sanuki no Miyatsuko discovers a miniature girl inside a glowing bamboo shoot. Believing her to be a divine presence, he and his wife decide to raise her as their own, calling her 'Princess'.",
"director": "Isao Takahata",
"producer": "Yoshiaki Nishimura",
"release_date": "2013",
"rt_score": "100",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/578ae244-7750-4d9f-867b-f3cd3d6fecf4"
},
{
"id": "5fdfb320-2a02-49a7-94ff-5ca418cae602",
"title": "When Marnie Was There",
"description": "The film follows Anna Sasaki living with her relatives in the seaside town. Anna comes across a nearby abandoned mansion, where she meets Marnie, a mysterious girl who asks her to promise to keep their secrets from everyone. As the summer progresses, Anna spends more time with Marnie, and eventually Anna learns the truth about her family and foster care.",
"director": "Hiromasa Yonebayashi",
"producer": "Yoshiaki Nishimura",
"release_date": "2014",
"rt_score": "92",
"people": [
"https://ghibliapi.herokuapp.com/people/"
],
"species": [
"https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2"
],
"locations": [
"https://ghibliapi.herokuapp.com/locations/"
],
"vehicles": [
"https://ghibliapi.herokuapp.com/vehicles/"
],
"url": "https://ghibliapi.herokuapp.com/films/5fdfb320-2a02-49a7-94ff-5ca418cae602"
}
]

563
test/fake-people.json Normal file
View File

@@ -0,0 +1,563 @@
[
{
"id": "fe93adf2-2f3a-4ec4-9f68-5422f1b87c01",
"name": "Pazu",
"gender": "Male",
"age": "13",
"eye_color": "Black",
"hair_color": "Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/fe93adf2-2f3a-4ec4-9f68-5422f1b87c01"
},
{
"id": "598f7048-74ff-41e0-92ef-87dc1ad980a9",
"name": "Lusheeta Toel Ul Laputa",
"gender": "Female",
"age": "13",
"eye_color": "Black",
"hair_color": "Black",
"films": [
"https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/598f7048-74ff-41e0-92ef-87dc1ad980a9"
},
{
"id": "3bc0b41e-3569-4d20-ae73-2da329bf0786",
"name": "Dola",
"gender": "Female",
"age": "60",
"eye_color": "Black",
"hair_color": "Peach",
"films": [
"https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/3bc0b41e-3569-4d20-ae73-2da329bf0786"
},
{
"id": "abe886e7-30c8-4c19-aaa5-d666e60d14de",
"name": "Romska Palo Ul Laputa",
"gender": "Male",
"age": "33",
"eye_color": "Black",
"hair_color": "Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/abe886e7-30c8-4c19-aaa5-d666e60d14de"
},
{
"id": "e08880d0-6938-44f3-b179-81947e7873fc",
"name": "Uncle Pom",
"gender": "Male",
"age": "Unspecified/Elderly",
"eye_color": "Black",
"hair_color": "White",
"films": [
"https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/e08880d0-6938-44f3-b179-81947e7873fc"
},
{
"id": "5c83c12a-62d5-4e92-8672-33ac76ae1fa0",
"name": "General Muoro",
"gender": "Male",
"age": "Unspecified/Adult",
"eye_color": "Black",
"hair_color": "None",
"films": [
"https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/5c83c12a-62d5-4e92-8672-33ac76ae1fa0"
},
{
"id": "3f4c408b-0bcc-45a0-bc8b-20ffc67a2ede",
"name": "Duffi",
"gender": "Male",
"age": "Unspecified/Adult",
"eye_color": "Dark brown",
"hair_color": "Dark brown",
"films": [
"https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/3f4c408b-0bcc-45a0-bc8b-20ffc67a2ede"
},
{
"id": "fcb4a2ac-5e41-4d54-9bba-33068db083ca",
"name": "Louis",
"gender": "Male",
"age": "30",
"eye_color": "Dark brown",
"hair_color": "Dark brown",
"films": [
"https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/fcb4a2ac-5e41-4d54-9bba-33068db083ca"
},
{
"id": "2cb76c15-772a-4cb3-9919-3652f56611d0",
"name": "Charles",
"gender": "Male",
"age": "Unspecified/Adult",
"eye_color": "Dark brown",
"hair_color": "Light brown",
"films": [
"https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/2cb76c15-772a-4cb3-9919-3652f56611d0"
},
{
"id": "f6f2c477-98aa-4796-b9aa-8209fdeed6b9",
"name": "Henri",
"gender": "Male",
"age": "Unspecified/Adult",
"eye_color": "Dark brown",
"hair_color": "Reddish brown",
"films": [
"https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/f6f2c477-98aa-4796-b9aa-8209fdeed6b9"
},
{
"id": "05d8d01b-0c2f-450e-9c55-aa0daa34838e",
"name": "Motro",
"gender": "Male",
"age": "Unspecified/Adult",
"eye_color": "Dark brown",
"hair_color": "None",
"films": [
"https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/05d8d01b-0c2f-450e-9c55-aa0daa34838e"
},
{
"id": "b22a684f-1819-40c8-94a6-d40c3b5e18eb",
"name": "Okami",
"gender": "Female",
"age": "50",
"eye_color": "Dark brown",
"hair_color": "Orange",
"films": [
"https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/b22a684f-1819-40c8-94a6-d40c3b5e18eb"
},
{
"id": "ba924631-068e-4436-b6de-f3283fa848f0",
"name": "Ashitaka",
"gender": "Male",
"age": "late teens",
"eye_color": "Brown",
"hair_color": "Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/ba924631-068e-4436-b6de-f3283fa848f0"
},
{
"id": "ebe40383-aad2-4208-90ab-698f00c581ab",
"name": "San",
"gender": "Female",
"age": "17",
"eye_color": "Brown",
"hair_color": "Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/ebe40383-aad2-4208-90ab-698f00c581ab"
},
{
"id": "34277bec-7401-43fa-a00a-5aee64b45b08",
"name": "Eboshi",
"gender": "Female",
"age": "Unspecified/Adult",
"eye_color": "Hazel",
"hair_color": "Black",
"films": [
"https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/34277bec-7401-43fa-a00a-5aee64b45b08"
},
{
"id": "91939012-90b9-46e5-a649-96b898073c82",
"name": "Jigo",
"gender": "Male",
"age": "Middle age",
"eye_color": "Black",
"hair_color": "Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/91939012-90b9-46e5-a649-96b898073c82"
},
{
"id": "20e3bd33-b35d-41e6-83a4-57ca7f028d38",
"name": "Kohroku",
"gender": "Male",
"age": "Adult",
"eye_color": "Black",
"hair_color": "Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/20e3bd33-b35d-41e6-83a4-57ca7f028d38"
},
{
"id": "8bccdc78-545b-49f4-a4c8-756163a38c91",
"name": "Gonza",
"gender": "Male",
"age": "Adult",
"eye_color": "Grey",
"hair_color": "Bald, but beard is Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/8bccdc78-545b-49f4-a4c8-756163a38c91"
},
{
"id": "116bfe1b-3ba8-4fa0-8f72-88537a493cb9",
"name": "Hii-sama",
"gender": "Female",
"age": "Over 50",
"eye_color": "Brown",
"hair_color": "White",
"films": [
"https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/116bfe1b-3ba8-4fa0-8f72-88537a493cb9"
},
{
"id": "030555b3-4c92-4fce-93fb-e70c3ae3df8b",
"name": "Yakul",
"age": "Unknown",
"gender": "Male",
"eye_color": "Grey",
"hair_color": "Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
],
"species": "https://ghibliapi.herokuapp.com/species/6bc92fdd-b0f4-4286-ad71-1f99fb4a0d1e",
"url": "https://ghibliapi.herokuapp.com/people/030555b3-4c92-4fce-93fb-e70c3ae3df8b"
},
{
"id": "ca568e87-4ce2-4afa-a6c5-51f4ae80a60b",
"name": "Shishigami",
"age": "400",
"gender": "Male",
"eye_color": "Red",
"hair_color": "Light Orange",
"films": [
"https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
],
"species": "https://ghibliapi.herokuapp.com/species/6bc92fdd-b0f4-4286-ad71-1f99fb4a0d1e",
"url": "https://ghibliapi.herokuapp.com/people/ca568e87-4ce2-4afa-a6c5-51f4ae80a60b"
},
{
"id": "e9356bb5-4d4a-4c93-aadc-c83e514bffe3",
"name": "Moro",
"gender": "Female",
"age": "300",
"eye_color": "Brown",
"hair_color": "White",
"films": [
"https://ghibliapi.herokuapp.com/films/0440483e-ca0e-4120-8c50-4c8cd9b965d6"
],
"species": "https://ghibliapi.herokuapp.com/species/f25fa661-3073-414d-968a-ab062e3065f7",
"url": "https://ghibliapi.herokuapp.com/people/e9356bb5-4d4a-4c93-aadc-c83e514bffe3"
},
{
"id": "7151abc6-1a9e-4e6a-9711-ddb50ea572ec",
"name": "Jiji",
"gender": "Male",
"age": "NA",
"eye_color": "Black",
"hair_color": "Black",
"films": [
"https://ghibliapi.herokuapp.com/films/ea660b10-85c4-4ae3-8a5f-41cea3648e3e"
],
"species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
"url": "https://ghibliapi.herokuapp.com/people/7151abc6-1a9e-4e6a-9711-ddb50ea572ec"
},
{
"id": "986faac6-67e3-4fb8-a9ee-bad077c2e7fe",
"name": "Satsuki Kusakabe",
"gender": "Female",
"age": "11",
"eye_color": "Dark Brown/Black",
"hair_color": "Dark Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/986faac6-67e3-4fb8-a9ee-bad077c2e7fe"
},
{
"id": "d5df3c04-f355-4038-833c-83bd3502b6b9",
"name": "Mei Kusakabe",
"gender": "Female",
"age": "4",
"eye_color": "Brown",
"hair_color": "Light Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/d5df3c04-f355-4038-833c-83bd3502b6b9"
},
{
"id": "3031caa8-eb1a-41c6-ab93-dd091b541e11",
"name": "Tatsuo Kusakabe",
"gender": "Male",
"age": "37",
"eye_color": "Brown",
"hair_color": "Dark Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/3031caa8-eb1a-41c6-ab93-dd091b541e11"
},
{
"id": "87b68b97-3774-495b-bf80-495a5f3e672d",
"name": "Yasuko Kusakabe",
"gender": "Female",
"age": "Adult",
"eye_color": "Brown",
"hair_color": "Dark Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/87b68b97-3774-495b-bf80-495a5f3e672d"
},
{
"id": "08ffbce4-7f94-476a-95bc-76d3c3969c19",
"name": "Granny",
"gender": "Female",
"age": "Elder",
"eye_color": "Black",
"hair_color": "Grey",
"films": [
"https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/08ffbce4-7f94-476a-95bc-76d3c3969c19"
},
{
"id": "0f8ef701-b4c7-4f15-bd15-368c7fe38d0a",
"name": "Kanta Ogaki",
"gender": "Male",
"age": "11",
"eye_color": "Brown",
"hair_color": "Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/0f8ef701-b4c7-4f15-bd15-368c7fe38d0a"
},
{
"id": "d39deecb-2bd0-4770-8b45-485f26e1381f",
"name": "Totoro",
"gender": "NA",
"age": "",
"eye_color": "Grey",
"hair_color": "Grey",
"films": [
"https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
],
"species": "https://ghibliapi.herokuapp.com/species/74b7f547-1577-4430-806c-c358c8b6bcf5",
"url": "https://ghibliapi.herokuapp.com/people/d39deecb-2bd0-4770-8b45-485f26e1381f"
},
{
"id": "591524bc-04fe-4e60-8d61-2425e42ffb2a",
"name": "Chu Totoro",
"gender": "NA",
"age": "",
"eye_color": "Black",
"hair_color": "Blue",
"films": [
"https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
],
"species": "https://ghibliapi.herokuapp.com/species/74b7f547-1577-4430-806c-c358c8b6bcf5",
"url": "https://ghibliapi.herokuapp.com/people/d39deecb-2bd0-4770-8b45-485f26e1381f"
},
{
"id": "c491755a-407d-4d6e-b58a-240ec78b5061",
"name": "Chibi Totoro",
"gender": "NA",
"age": "",
"eye_color": "Black",
"hair_color": "White",
"films": [
"https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
],
"species": "https://ghibliapi.herokuapp.com/species/74b7f547-1577-4430-806c-c358c8b6bcf5",
"url": "https://ghibliapi.herokuapp.com/people/d39deecb-2bd0-4770-8b45-485f26e1381f"
},
{
"id": "f467e18e-3694-409f-bdb3-be891ade1106",
"name": "Catbus",
"gender": "Male",
"age": "NA",
"eye_color": "Yellow",
"hair_color": "Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/58611129-2dbc-4a81-a72f-77ddfc1b1b49"
],
"species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
"url": "https://ghibliapi.herokuapp.com/people/f467e18e-3694-409f-bdb3-be891ade1106"
},
{
"id": "89026b3a-abc4-4053-ab1a-c6d2eea68faa",
"name": "Niya",
"gender": "Male",
"age": "NA",
"eye_color": "White",
"hair_color": "Beige",
"films": [
"https://ghibliapi.herokuapp.com/films/2de9426b-914a-4a06-a3a0-5e6d9d3886f6"
],
"species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
"url": "https://ghibliapi.herokuapp.com/people/89026b3a-abc4-4053-ab1a-c6d2eea68faa"
},
{
"id": "6b3facea-ea33-47b1-96ce-3fc737b119b8",
"name": "Renaldo Moon aka Moon aka Muta",
"gender": "Male",
"age": "NA",
"eye_color": "White",
"hair_color": "Beige",
"films": [
"https://ghibliapi.herokuapp.com/films/90b72513-afd4-4570-84de-a56c312fdf81",
"https://ghibliapi.herokuapp.com/films/ff24da26-a969-4f0e-ba1e-a122ead6c6e3"
],
"species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
"url": "https://ghibliapi.herokuapp.com/people/6b3facea-ea33-47b1-96ce-3fc737b119b8"
},
{
"id": "3042818d-a8bb-4cba-8180-c19249822d57",
"name": "Cat King",
"gender": "Male",
"age": "87",
"eye_color": "Emerald",
"hair_color": "Grey",
"films": [
"https://ghibliapi.herokuapp.com/films/90b72513-afd4-4570-84de-a56c312fdf81"
],
"species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
"url": "https://ghibliapi.herokuapp.com/people/3042818d-a8bb-4cba-8180-c19249822d57"
},
{
"id": "58d1973f-f247-47d7-9358-e56cb0d2b5a6",
"name": "Yuki",
"gender": "Female",
"age": "NA",
"eye_color": "Blue",
"hair_color": "White",
"films": [
"https://ghibliapi.herokuapp.com/films/90b72513-afd4-4570-84de-a56c312fdf81"
],
"species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
"url": "https://ghibliapi.herokuapp.com/people/58d1973f-f247-47d7-9358-e56cb0d2b5a6"
},
{
"id": "a3d8e70f-46a0-4e5a-b850-db01620d6b92",
"name": "Haru",
"gender": "Female",
"age": "13",
"eye_color": "Brown",
"hair_color": "Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/90b72513-afd4-4570-84de-a56c312fdf81"
],
"species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
"url": "https://ghibliapi.herokuapp.com/people/a3d8e70f-46a0-4e5a-b850-db01620d6b92"
},
{
"id": "fc196c4f-0201-4ed2-9add-c6403f7c4d32",
"name": "Baron Humbert von Gikkingen",
"gender": "Male",
"age": "NA",
"eye_color": "Green",
"hair_color": "Yellow",
"films": [
"https://ghibliapi.herokuapp.com/films/ff24da26-a969-4f0e-ba1e-a122ead6c6e3",
"https://ghibliapi.herokuapp.com/films/90b72513-afd4-4570-84de-a56c312fdf81"
],
"species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
"url": "https://ghibliapi.herokuapp.com/people/fc196c4f-0201-4ed2-9add-c6403f7c4d32"
},
{
"id": "466bc926-2024-4653-ac63-fe52f2dc8c7b",
"name": "Natori",
"gender": "Male",
"age": "NA",
"eye_color": "Blue",
"hair_color": "Grey",
"films": [
"https://ghibliapi.herokuapp.com/films/90b72513-afd4-4570-84de-a56c312fdf81"
],
"species": "https://ghibliapi.herokuapp.com/species/603428ba-8a86-4b0b-a9f1-65df6abef3d3",
"url": "https://ghibliapi.herokuapp.com/people/466bc926-2024-4653-ac63-fe52f2dc8c7b"
},
{
"id": "40c005ce-3725-4f15-8409-3e1b1b14b583",
"name": "Colonel Muska",
"gender": "Male",
"age": "33",
"eye_color": "Grey",
"hair_color": "Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/2baf70d1-42bb-4437-b551-e5fed5a87abe"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/40c005ce-3725-4f15-8409-3e1b1b14b583"
},
{
"id": "6523068d-f5a9-4150-bf5b-76abe6fb42c3",
"name": "Porco Rosso",
"gender": "Male",
"age": "47",
"eye_color": "Black",
"hair_color": "Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/ebbb6b7c-945c-41ee-a792-de0e43191bd8"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/6523068d-f5a9-4150-bf5b-76abe6fb42c3"
},
{
"id": "a10f64f3-e0b6-4a94-bf30-87ad8bc51607",
"name": "Sosuke",
"gender": "Male",
"age": "5",
"eye_color": "Brown",
"hair_color": "Brown",
"films": [
"https://ghibliapi.herokuapp.com/films/758bf02e-3122-46e0-884e-67cf83df1786"
],
"species": "https://ghibliapi.herokuapp.com/species/af3910a6-429f-4c74-9ad5-dfe1c4aa04f2",
"url": "https://ghibliapi.herokuapp.com/people/a10f64f3-e0b6-4a94-bf30-87ad8bc51607"
}
]