diff --git a/.editorconfig b/.editorconfig index cc08eda..4bcfacd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,7 +3,7 @@ root = true [*] charset = utf-8 indent_size = 2 -indent_style = space +indent_style = tab insert_final_newline = true max_line_length = 100 trim_trailing_whitespace = true @@ -15,4 +15,4 @@ trim_trailing_whitespace = false indent_style = tab [*.scss] indent_size = 2 -indent_style = space \ No newline at end of file +indent_style = tab \ No newline at end of file diff --git a/.eslintignore b/.eslintignore index a784101..6d2eaaa 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,11 +1 @@ -./README.md - -node_modules/* - -*.test.js - -package.json - -package-lock.json - -./.github/workflows/**/*.yml \ No newline at end of file +.github/ \ No newline at end of file diff --git a/.eslintrc b/.eslintrc index 332e220..1fb4f5e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,66 +1,107 @@ { - "env": { - "browser": true, - "commonjs": true, - "es2020": true - }, - "extends": ["airbnb-base", "prettier", "plugin:import/errors", "plugin:import/warnings"], - "plugins": ["prettier"], + "root": true, + "parser": "@typescript-eslint/parser", "parserOptions": { - "ecmaVersion": 11 + "project": true, + "tsconfigRootDir": ".", + "sourceType": "module" }, + "plugins": ["@typescript-eslint", "sonarjs", "prettier"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "airbnb-base", + "airbnb-typescript/base", + "plugin:sonarjs/recommended", + "plugin:@typescript-eslint/strict-type-checked", + "plugin:@typescript-eslint/stylistic-type-checked", + "plugin:prettier/recommended" + ], + "ignorePatterns": ["**/node_modules/**", "dist/**", "*.graphql"], "rules": { - "array-callback-return": "off", - "import/no-extraneous-dependencies": [ + "@typescript-eslint/lines-between-class-members": "off", + "@typescript-eslint/no-throw-literal": "off", + "@typescript-eslint/restrict-template-expressions": "off", + "@typescript-eslint/no-duplicate-enum-values": "off", + // These rules are for reference only. + //#region eslint + "class-methods-use-this": "off", + // https://github.com/typescript-eslint/typescript-eslint/issues/1277 + "consistent-return": "off", + "func-names": "off", + "max-len": ["error", { "code": 140, "ignoreTemplateLiterals": true, "ignoreUrls": true }], + "newline-per-chained-call": "off", + "no-await-in-loop": "off", + "no-continue": "off", + // https://github.com/airbnb/javascript/issues/1342 + "no-param-reassign": ["error", { "props": false }], + // https://github.com/airbnb/javascript/issues/1271 + // https://github.com/airbnb/javascript/blob/fd77bbebb77362ddecfef7aba3bf6abf7bdd81f2/packages/eslint-config-airbnb-base/rules/style.js#L340-L358 + "no-restricted-syntax": ["error", "ForInStatement", "LabeledStatement", "WithStatement"], + "no-underscore-dangle": ["error", { "allow": ["_id"] }], + "no-void": ["error", { "allowAsStatement": true }], + "object-curly-newline": "off", + "spaced-comment": [ "error", - { - "devDependencies": ["**/*.test.js"] - } + "always", + { "line": { "markers": ["/", "#region", "#endregion"] } } ], - "no-process-exit": "error", - "no-debugger": 0, - "no-alert": 0, - "no-await-in-loop": 0, - "no-return-assign": ["error", "except-parens"], - "no-restricted-syntax": [2, "ForInStatement", "LabeledStatement", "WithStatement"], - "no-unused-vars": "error", - "prefer-const": [ + //#endregion + + //#region import + "import/extensions": ["error", "never"], + // https://github.com/benmosher/eslint-plugin-import/issues/1753 + "import/named": "off", + "import/no-default-export": "error", + "import/order": [ "error", { - "destructuring": "all" + "groups": [["builtin", "external", "internal"]], + "newlines-between": "always", + "alphabetize": { "order": "asc", "caseInsensitive": true } } ], - "arrow-body-style": [2, "as-needed"], - "no-unused-expressions": [ - 2, - { - "allowTaggedTemplates": true - } + "import/prefer-default-export": "off", + //#endregion + + //#region @typescript-eslint + "@typescript-eslint/consistent-type-assertions": [ + "error", + { "assertionStyle": "angle-bracket" } ], - "no-param-reassign": [ - 2, + "@typescript-eslint/naming-convention": [ + "error", + { "selector": "default", "format": ["strictCamelCase"] }, + { "selector": "variable", "format": ["strictCamelCase", "UPPER_CASE", "StrictPascalCase"] }, + // https://github.com/microsoft/TypeScript/issues/9458 { - "props": false - } + "selector": "parameter", + "modifiers": ["unused"], + "format": ["strictCamelCase"], + "leadingUnderscore": "allow" + }, + { "selector": "property", "format": null }, + { "selector": "typeProperty", "format": null }, + { "selector": "typeLike", "format": ["StrictPascalCase"] }, + { "selector": "enumMember", "format": ["UPPER_CASE"] } ], - "no-console": 0, - "default-case": 0, - "import/prefer-default-export": 0, - "func-names": "off", - "space-before-function-paren": 0, - "comma-dangle": 0, - "max-len": 0, - "import/extensions": 2, - "no-underscore-dangle": 0, - "consistent-return": 0, - "radix": 0, - "no-shadow": [ - 2, + "@typescript-eslint/no-extraneous-class": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + //#endregion + + //#region sonarjs + "sonarjs/no-duplicate-string": "off", + "sonarjs/cognitive-complexity": ["error", 25], + //#endregion + + "prettier/prettier": [ + "error", { - "hoist": "all", - "allow": ["resolve", "reject", "done", "next", "err", "error"] + "singleQuote": true, + "endOfLine": "auto" } ], + "quotes": [ 2, "single", @@ -69,11 +110,11 @@ "allowTemplateLiterals": true } ], - "prettier/prettier": [ + + "@typescript-eslint/array-type": [ "error", { - "singleQuote": true, - "endOfLine": "auto" + "default": "generic" } ] } diff --git a/.github/workflows/greeting-action.yml b/.github/workflows/greeting-action.yml deleted file mode 100644 index 897eb5a..0000000 --- a/.github/workflows/greeting-action.yml +++ /dev/null @@ -1,22 +0,0 @@ -# Job name -name: Greeting first time users - -# When the to run the greeting -on: [pull_request, issues] - -# Jobs to run for the action (You can have multiple actions in one file) -jobs: - greet-user: - # Job display name - name: Greeting Action - - # Runs on a Linux based OS - runs-on: ubuntu-latest - - # Runs the greeting action (Options can be found here https://github.com/actions/first-interaction) - steps: - - uses: actions/first-interaction@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} # Authorizes the github action via a Github token which is for this repo only (Generated automatically) - issue-message: "Looks like this is your first issue' first issue" # Shows when a user fires their first issue - pr-message: "Looks like this is your first PR' first pr" # Shows when a user fires their first PR diff --git a/.github/workflows/linting-action.yml b/.github/workflows/linting-action.yml index 94180c4..42c9e0e 100644 --- a/.github/workflows/linting-action.yml +++ b/.github/workflows/linting-action.yml @@ -1,64 +1,50 @@ -# Job name -name: Application Linting +name: Lint -# When the to run the greeting on: [pull_request, push] -# Jobs to run for the action (You can have multiple actions in one file) jobs: run-linters: - # Job display name - name: Running the eslint checks - - # Runs on a Linux based OS + name: Run linters runs-on: ubuntu-latest + # Run the job on Node 20 and 23 which better support modern testing frameworks + strategy: + matrix: + node-version: [20.x, 21.x, 23.x] + # Steps involved for this particular task steps: - # Checks out the reporsitoy and enables the use of commands made avaliable in the project ie npm run + # Checks out the repository and enables the use of commands made available in the project ie npm run - name: Check out Git repository uses: actions/checkout@v2 - # Setup Nodes on the versions specified in the matrix stratergy - - name: Set up Node.js version + - name: Set up Node.js version ${{ matrix.node-version }} uses: actions/setup-node@v1 with: - node-version: 12 + node-version: ${{ matrix.node-version }} - # Cache the node_modules - - name: Cache node modules - uses: actions/cache@v2 + - name: Enable Corepack + run: corepack enable - # Defining the cache env config ie the key - env: - cache-name: cache-node-modules + - name: Set Yarn version + run: yarn set version 4.2.2 - # Caching options (https://github.com/actions/cache) + - name: Cache yarn dependencies + uses: actions/cache@v2 + env: + cache-name: cache-yarn-dependencies with: - # npm cache files are stored in `~/.npm` on Linux/macOS - path: ~/.npm - - # Key for caching the files initally - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - - # Restore keys + path: | + .yarn/cache + .yarn/unplugged + .yarn/build-state.yml + .yarn/install-state.gz + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- + ${{ runner.os }}-yarn- - # Installs all the project dependencies e.g. prettier, eslint etc via a custom project script - - name: Install Node.js dependencies - run: npm run allDependencies + - name: Install dependencies + run: yarn install - # Run the linting action - name: Run linters - uses: samuelmeuli/lint-action@v1 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} # Authorizes the github action via a Github token which is for this repo only (Generated automatically) - eslint: true # Enables eslint - prettier: true # Enables prettier to prettieifer the code when commiting and spots any violations - auto_fix: true # Auto fixes an prettier or eslint violations - git_name: 'TheOpenMovieDB-GraphQL-Example BOT' - git_email: 'AlexMachin1997@gmail.com' - commit_message: 'TheOpenMovieDB-GraphQL-Example BOT has fixed an eslint/prettier issue' # Might need removing if it causes the linting to break + run: yarn lint diff --git a/.github/workflows/test-action.yml b/.github/workflows/test-action.yml index 723aa0a..e3b26d1 100644 --- a/.github/workflows/test-action.yml +++ b/.github/workflows/test-action.yml @@ -8,15 +8,15 @@ on: [pull_request, push] jobs: test: # Job display name - name: Running the jest tests + name: Running Vitest tests # Runs on a Linux based OS runs-on: ubuntu-latest - # Run the job on 2 different versions of Node (12, 14) + # Run the job on Node 20 and 23 which better support modern testing frameworks strategy: matrix: - node-version: [12.x, 14.x] + node-version: [20.x, 21.x, 23.x] # Steps involved for this particular task steps: @@ -30,35 +30,36 @@ jobs: with: node-version: ${{ matrix.node-version }} - # Cache the node_modules - - name: Cache node modules - uses: actions/cache@v2 + # Enable Corepack for Yarn + - name: Enable Corepack + run: corepack enable - # Defining the cache env config ie the key - env: - cache-name: cache-node-modules + # Set Yarn version + - name: Set Yarn version + run: yarn set version 4.2.2 - # Caching options (https://github.com/actions/cache) + # Cache the yarn dependencies + - name: Cache yarn dependencies + uses: actions/cache@v2 + env: + cache-name: cache-yarn-dependencies with: - # npm cache files are stored in `~/.npm` on Linux/macOS - path: ~/.npm - - # Key for caching the files initally - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - - # Restore keys + path: | + .yarn/cache + .yarn/unplugged + .yarn/build-state.yml + .yarn/install-state.gz + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- + ${{ runner.os }}-yarn- - # Installs all the project dependencies e.g. prettier, eslint etc via a custom project script - - name: Install Node.js dependencies - run: npm run allDependencies + # Install dependencies using Yarn + - name: Install dependencies + run: yarn install - # Run the react-testing-library tests + # Run the Vitest tests - name: Run all tests env: OPEN_MOVIE_DB_API_URI: ${{ secrets.OPEN_MOVIE_DB_API_URI }} OPEN_MOVIE_DB_API_KEY: ${{ secrets.OPEN_MOVIE_DB_API_KEY }} - run: npm run test + run: yarn test diff --git a/.gitignore b/.gitignore index 01eb0ba..6a7d6d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,14 @@ -### Node ### # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids @@ -12,14 +16,17 @@ pids *.seed *.pid.lock +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov # Coverage directory used by tools like istanbul coverage +*.lcov # nyc test coverage .nyc_output -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) @@ -28,15 +35,18 @@ bower_components # node-waf configuration .lock-wscript -# Compiled binary addons (http://nodejs.org/api/addons.html) +# Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ -# Typescript v1 declaration files -typings/ +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo # Optional npm cache directory .npm @@ -44,6 +54,15 @@ typings/ # Optional eslint cache .eslintcache +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + # Optional REPL history .node_repl_history @@ -53,8 +72,59 @@ typings/ # Yarn Integrity file .yarn-integrity -# dotenv environment variables file +# dotenv environment variable files .env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test -#database files -data/ \ No newline at end of file +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..0ab216a --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +yarn validate \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index 56dd05a..6d2eaaa 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1 @@ -./.github/workflows/**/*.yml \ No newline at end of file +.github/ \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index e75fe30..a521039 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,8 +1,10 @@ { - "printWidth": 100, - "trailingComma": "none", - "tabWidth": 2, - "semi": true, - "singleQuote": true, - "useTabs": true + "printWidth": 100, + "trailingComma": "none", + "tabWidth": 2, + "semi": true, + "singleQuote": true, + "useTabs": true, + "jsxSingleQuote": true, + "endOfLine": "lf" } diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index a7549e7..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "recommendations": [ - "dbaeumer.vscode-eslint", - "esbenp.prettier-vscode", - "VisualStudioExptTeam.vscodeintellicode", - "christian-kohler.npm-intellisense", - "letrieu.expand-region", - "formulahendry.auto-rename-tag", - "christian-kohler.path-intellisense", - "2gua.rainbow-brackets", - "PKief.material-icon-theme", - "orta.vscode-jest", - "kumar-harsh.graphql-for-vscode", - "jpoissonnier.vscode-styled-components", - "ionutvmi.path-autocomplete", - "emmanuelbeziat.vscode-great-icons", - "vincaslt.highlight-matching-tag", - "eamodio.gitlens" - ] -} diff --git a/.vscode/settings.json b/.vscode/settings.json index b9bcebb..19085ee 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,6 @@ { + "scss.lint.unknownAtRules": "ignore", + "files.eol": "\n", "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, "editor.formatOnPaste": true, @@ -9,20 +11,23 @@ "editor.acceptSuggestionOnCommitCharacter": false, "editor.renderWhitespace": "boundary", "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, - "editor.cursorSmoothCaretAnimation": true, - "editor.fontSize": 20, + "editor.cursorSmoothCaretAnimation": "on", + "editor.fontSize": 15, + "editor.minimap.enabled": false, "editor.tabSize": 2, "files.trimTrailingWhitespace": true, "files.trimFinalNewlines": true, - "workbench.sideBar.location": "left", + "workbench.sideBar.location": "right", "javascript.updateImportsOnFileMove.enabled": "always", + "typescript.validate.enable": true, + "files.autoSave": "off", "files.exclude": { "USE_GITIGNORE": true @@ -42,10 +47,7 @@ "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], - "javascript.implicitProjectConfig.checkJs": true, - "breadcrumbs.enabled": true, - "grunt.autoDetect": "off", "npm.runSilent": true, "explorer.confirmDragAndDrop": true, @@ -61,7 +63,10 @@ "files.associations": { "*.react.js": "javascriptreact", - "*.jsx": "javascriptreact", - "*.js": "javascriptreact" - } + "*.jsx": "javascriptreact" + }, + "terminal.integrated.fontSize": 15, + "screencastMode.fontSize": 59, + "scm.inputFontSize": 15, + "typescript.tsdk": "node_modules\\typescript\\lib" } diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz new file mode 100644 index 0000000..72a3a64 Binary files /dev/null and b/.yarn/install-state.gz differ diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..0acb263 --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/ActingGroup.json b/ActingGroup.json new file mode 100644 index 0000000..d592925 --- /dev/null +++ b/ActingGroup.json @@ -0,0 +1,1519 @@ +[ + { + "year": "-", + "credits": [ + { + "mediaType": "tv", + "character": "Madison 'Madi' Cowart", + "title": "Just Cause", + "episodeCount": 1, + "year": "-", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "", + "title": "Tower of Terror", + "year": "-", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "The Bride", + "title": "Bride", + "year": "-", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Cousin Hilda", + "title": "The Phoenician Scheme", + "year": "-", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Augustina 'Gus' Mally", + "title": "The Gauntlet", + "year": "-", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "", + "title": "The Sea Change", + "year": "-", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Carol Blevins", + "title": "Featherwood", + "year": "-", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "", + "title": "Paris Paramount", + "year": "-", + "type": "cast" + } + ] + }, + { + "year": "2003", + "credits": [ + { + "mediaType": "movie", + "character": "Charlotte", + "title": "Lost in Translation", + "year": "2003", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Griet", + "title": "Girl with a Pearl Earring", + "year": "2003", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Jimmy Kimmel Live!", + "episodeCount": 3, + "year": "2003", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "The Sharon Osbourne Show", + "episodeCount": 1, + "year": "2003", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "The Ellen DeGeneres Show", + "episodeCount": 3, + "year": "2003", + "type": "cast" + } + ] + }, + { + "year": "2006", + "credits": [ + { + "mediaType": "movie", + "character": "Sondra Pransky", + "title": "Scoop", + "year": "2006", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Olivia Wenscombe", + "title": "The Prestige", + "year": "2006", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Kay Lake", + "title": "The Black Dahlia", + "year": "2006", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Close Up", + "episodeCount": 1, + "year": "2006", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Made in Hollywood: Teen Edition", + "episodeCount": 1, + "year": "2006", + "type": "cast" + } + ] + }, + { + "year": "1998", + "credits": [ + { + "mediaType": "movie", + "character": "Grace MacLean", + "title": "The Horse Whisperer", + "year": "1998", + "type": "cast" + } + ] + }, + { + "year": "2010", + "credits": [ + { + "mediaType": "movie", + "character": "Natalie Rushman / Natasha Romanoff", + "title": "Iron Man 2", + "year": "2010", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Daybreak", + "episodeCount": 1, + "year": "2010", + "type": "cast" + } + ] + }, + { + "year": "2001", + "credits": [ + { + "mediaType": "movie", + "character": "Rebecca", + "title": "Ghost World", + "year": "2001", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Birdy Abundas", + "title": "The Man Who Wasn't There", + "year": "2001", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Suzanne Sandor", + "title": "An American Rhapsody", + "year": "2001", + "type": "cast" + } + ] + }, + { + "year": "2009", + "credits": [ + { + "mediaType": "movie", + "character": "Anna Marks", + "title": "He's Just Not That Into You", + "year": "2009", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Self (archive footage)", + "title": "Saturday Night Live: The Best of Amy Poehler", + "year": "2009", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self - Special Guest", + "title": "RuPaul's Drag Race", + "episodeCount": 1, + "year": "2009", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self (archive footage)", + "title": "RuPaul's Drag Race", + "episodeCount": 1, + "year": "2009", + "type": "cast" + } + ] + }, + { + "year": "2005", + "credits": [ + { + "mediaType": "movie", + "character": "Jordan Two Delta / Sarah Jordan", + "title": "The Island", + "year": "2005", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Nola Rice", + "title": "Match Point", + "year": "2005", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Made in Hollywood", + "episodeCount": 2, + "year": "2005", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Showbiz Tonight", + "episodeCount": 1, + "year": "2005", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Tooth Fairy (voice)", + "title": "Robot Chicken", + "episodeCount": 1, + "year": "2005", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Sailor Moon, Self, Della Bea Robinson (voice)", + "title": "Robot Chicken", + "episodeCount": 1, + "year": "2005", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Veronica Lodge, Dolores (voice)", + "title": "Robot Chicken", + "episodeCount": 1, + "year": "2005", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Amy, Darlene, Cheerleader Stick (voice)", + "title": "Robot Chicken", + "episodeCount": 1, + "year": "2005", + "type": "cast" + } + ] + }, + { + "year": "2004", + "credits": [ + { + "mediaType": "movie", + "character": "Alex", + "title": "In Good Company", + "year": "2004", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Mindy (voice)", + "title": "The SpongeBob SquarePants Movie", + "year": "2004", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Pursy Will", + "title": "A Love Song for Bobby Long", + "year": "2004", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Francesca Curtis", + "title": "The Perfect Score", + "year": "2004", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Meg Windermere", + "title": "A Good Woman", + "year": "2004", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Self", + "title": "Lost on Location: Behind the Scenes of 'Lost in Translation'", + "year": "2004", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "omg! Insider", + "episodeCount": 2, + "year": "2004", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Entourage", + "episodeCount": 1, + "year": "2004", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Tinseltown TV", + "episodeCount": 1, + "year": "2004", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Le Grand Journal", + "episodeCount": 2, + "year": "2004", + "type": "cast" + } + ] + }, + { + "year": "1996", + "credits": [ + { + "mediaType": "movie", + "character": "Emily", + "title": "If Lucy Fell", + "year": "1996", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Amanda", + "title": "Manny & Lo", + "year": "1996", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self - Guest", + "title": "The Rosie O'Donnell Show", + "episodeCount": 1, + "year": "1996", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Critics Choice Awards", + "episodeCount": 1, + "year": "1996", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "The Daily Show", + "episodeCount": 1, + "year": "1996", + "type": "cast" + } + ] + }, + { + "year": "2008", + "credits": [ + { + "mediaType": "movie", + "character": "Cristina", + "title": "Vicky Cristina Barcelona", + "year": "2008", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Silken Floss", + "title": "The Spirit", + "year": "2008", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Mary Boleyn", + "title": "The Other Boleyn Girl", + "year": "2008", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Self", + "title": "Translating History to Screen", + "year": "2008", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Stand Up to Cancer", + "episodeCount": 1, + "year": "2008", + "type": "cast" + } + ] + }, + { + "year": "2012", + "credits": [ + { + "mediaType": "movie", + "character": "Natasha Romanoff / Black Widow", + "title": "The Avengers", + "year": "2012", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "(archive footage)", + "title": "Final Cut: Ladies and Gentlemen", + "year": "2012", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Janet Leigh", + "title": "Hitchcock", + "year": "2012", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Narrator (voice)", + "title": "Escape from the World's Most Dangerous Place", + "year": "2012", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Self", + "title": "The Avengers: A Visual Journey", + "year": "2012", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Finding Your Roots", + "episodeCount": 2, + "year": "2012", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Natasha Romanoff / Black Widow (archive footage)", + "title": "Honest Trailers", + "episodeCount": 1, + "year": "2012", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self - Interviewee", + "title": "Buenas noches y Buenafuente", + "episodeCount": 1, + "year": "2012", + "type": "cast" + } + ] + }, + { + "year": "2002", + "credits": [ + { + "mediaType": "movie", + "character": "Ashley Parker", + "title": "Eight Legged Freaks", + "year": "2002", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Self", + "title": "Drive-Thru Records: Vol. 1", + "year": "2002", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "E! Live from the Red Carpet", + "episodeCount": 2, + "year": "2002", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Ant & Dec's Saturday Night Takeaway", + "episodeCount": 1, + "year": "2002", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Silenci?", + "episodeCount": 1, + "year": "2002", + "type": "cast" + } + ] + }, + { + "year": "1995", + "credits": [ + { + "mediaType": "movie", + "character": "Kate Armstrong", + "title": "Just Cause", + "year": "1995", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Jenna Halliwell", + "title": "The Client", + "episodeCount": 1, + "year": "1995", + "type": "cast" + } + ] + }, + { + "year": "1997", + "credits": [ + { + "mediaType": "movie", + "character": "Molly Pruitt", + "title": "Home Alone 3", + "year": "1997", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Little Girl", + "title": "Fall", + "year": "1997", + "type": "cast" + } + ] + }, + { + "year": "2007", + "credits": [ + { + "mediaType": "movie", + "character": "Annie Braddock", + "title": "The Nanny Diaries", + "year": "2007", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Self", + "title": "The Director's Notebook: The Cinematic Sleight of Hand of Christopher Nolan", + "year": "2007", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Up Close with Carrie Keagan", + "episodeCount": 1, + "year": "2007", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Guys Choice Awards", + "episodeCount": 2, + "year": "2007", + "type": "cast" + } + ] + }, + { + "year": "2024", + "credits": [ + { + "mediaType": "movie", + "character": "Elita-1 (voice)", + "title": "Transformers One", + "year": "2024", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Kelly Jones", + "title": "Fly Me to the Moon", + "year": "2024", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Narrator (voice)", + "title": "Catching Fire: The Story of Anita Pallenberg", + "year": "2024", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Ash (voice)", + "title": "Sing: Thriller", + "year": "2024", + "type": "cast" + } + ] + }, + { + "year": "2011", + "credits": [ + { + "mediaType": "movie", + "character": "Self", + "title": "Woody Allen: A Documentary", + "year": "2011", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Kelly Foster", + "title": "We Bought a Zoo", + "year": "2011", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Self (archive footage)", + "title": "Bert Stern: Original Madman", + "year": "2011", + "type": "cast" + } + ] + }, + { + "year": "2021", + "credits": [ + { + "mediaType": "movie", + "character": "Ash (voice)", + "title": "Come Home", + "year": "2021", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Natasha Romanoff / Black Widow", + "title": "Black Widow", + "year": "2021", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Ash (voice)", + "title": "Sing 2", + "year": "2021", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Narrator (voice)", + "title": "Moneymaker: Behind Black Widow", + "year": "2021", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Natasha Romanoff / Black Widow (archive footage) (uncredited)", + "title": "Marvel Studios Legends", + "episodeCount": 7, + "year": "2021", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Marvel Studios Assembled", + "episodeCount": 1, + "year": "2021", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Natasha Romanoff / Black Widow (archive footage) (uncredited)", + "title": "Hawkeye", + "episodeCount": 2, + "year": "2021", + "type": "cast" + } + ] + }, + { + "year": "1999", + "credits": [ + { + "mediaType": "movie", + "character": "Kathy Caldwell", + "title": "My Brother the Pig", + "year": "1999", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self - Guest", + "title": "The Late Late Show with Craig Kilborn", + "episodeCount": 1, + "year": "1999", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "The Early Show", + "episodeCount": 1, + "year": "1999", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Teen Choice Awards", + "episodeCount": 1, + "year": "1999", + "type": "cast" + } + ] + }, + { + "year": "2022", + "credits": [ + { + "mediaType": "movie", + "character": "Self (archive footage)", + "title": "Marvel Studios Assembled: The Making of Hawkeye", + "year": "2022", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Narrator (voice)", + "title": "Penglai", + "year": "2022", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "", + "title": "StoryBots: Answer Time", + "episodeCount": 1, + "year": "2022", + "type": "cast" + } + ] + }, + { + "year": "2014", + "credits": [ + { + "mediaType": "movie", + "character": "Natasha Romanoff / Black Widow", + "title": "Captain America: The Winter Soldier", + "year": "2014", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "", + "title": "Under the Skin", + "year": "2014", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Molly", + "title": "Chef", + "year": "2014", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Lucy Miller", + "title": "Lucy", + "year": "2014", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Narrator (voice)", + "title": "Deep Down", + "year": "2014", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Self (archive footage)", + "title": "Her: Love in the Modern Age", + "year": "2014", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Self", + "title": "Marvel: 75 Years, from Pulp to Pop!", + "year": "2014", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Self", + "title": "Marvel Studios: Assembling a Universe", + "year": "2014", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Olivia (segment \"Two Player Game\") (voice)", + "title": "HitRECord on TV with Joseph Gordon-Levitt", + "episodeCount": 1, + "year": "2014", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "The Tonight Show Starring Jimmy Fallon", + "episodeCount": 3, + "year": "2014", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Variety Studio: Actors on Actors", + "episodeCount": 1, + "year": "2014", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Late Night with Seth Meyers", + "episodeCount": 1, + "year": "2014", + "type": "cast" + } + ] + }, + { + "year": "2015", + "credits": [ + { + "mediaType": "movie", + "character": "Natasha Romanoff / Black Widow", + "title": "Avengers: Age of Ultron", + "year": "2015", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Natasha Romanoff / Black Widow", + "title": "WHIH Newsfront", + "episodeCount": 4, + "year": "2015", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Pink Lady (voice)", + "title": "Assassin Banana", + "episodeCount": 2, + "year": "2015", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "The Late Show with Stephen Colbert", + "episodeCount": 1, + "year": "2015", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Close Up with The Hollywood Reporter", + "episodeCount": 1, + "year": "2015", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Hot Ones", + "episodeCount": 1, + "year": "2015", + "type": "cast" + } + ] + }, + { + "year": "2013", + "credits": [ + { + "mediaType": "movie", + "character": "Samantha (voice)", + "title": "Her", + "year": "2013", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Barbara Sugarman", + "title": "Don Jon", + "year": "2013", + "type": "cast" + } + ] + }, + { + "year": "2023", + "credits": [ + { + "mediaType": "movie", + "character": "Katherine", + "title": "North Star", + "year": "2023", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Midge Campbell", + "title": "Asteroid City", + "year": "2023", + "type": "cast" + } + ] + }, + { + "year": "2016", + "credits": [ + { + "mediaType": "movie", + "character": "Kaa (voice)", + "title": "The Jungle Book", + "year": "2016", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "DeeAnna Moran", + "title": "Hail, Caesar!", + "year": "2016", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Natasha Romanoff / Black Widow", + "title": "Captain America: Civil War", + "year": "2016", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Ash (voice)", + "title": "Sing", + "year": "2016", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Herself", + "title": "Floyd Norman: An Animated Life", + "year": "2016", + "type": "cast" + } + ] + }, + { + "year": "2017", + "credits": [ + { + "mediaType": "movie", + "character": "Major Mira Killian / Motoko Kusanagi", + "title": "Ghost in the Shell", + "year": "2017", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Self", + "title": "Broadway: The Next Generation", + "year": "2017", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Jess", + "title": "Rough Night", + "year": "2017", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "self", + "title": "Ghost in the Shell: Hard-Wired Humanity - Making Ghost in the Shell", + "year": "2017", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Self", + "title": "Art as Dialogue", + "year": "2017", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Narrator (voice)", + "title": "Jeff Koons", + "year": "2017", + "type": "cast" + } + ] + }, + { + "year": "2020", + "credits": [ + { + "mediaType": "movie", + "character": "Self", + "title": "VOMO: Vote or Miss Out", + "year": "2020", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Self - Actress, \"Avengers: Endgame\"", + "title": "Chadwick Boseman: A Tribute for a King", + "year": "2020", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "The Drew Barrymore Show", + "episodeCount": 1, + "year": "2020", + "type": "cast" + } + ] + }, + { + "year": "1994", + "credits": [ + { + "mediaType": "movie", + "character": "Laura Nelson", + "title": "North", + "year": "1994", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Cartelera", + "episodeCount": 1, + "year": "1994", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Extra", + "episodeCount": 1, + "year": "1994", + "type": "cast" + } + ] + }, + { + "year": "2025", + "credits": [ + { + "mediaType": "movie", + "character": "Zora Bennett", + "title": "Jurassic World Rebirth", + "year": "2025", + "type": "cast" + } + ] + }, + { + "year": "2018", + "credits": [ + { + "mediaType": "movie", + "character": "Natasha Romanoff / Black Widow", + "title": "Avengers: Infinity War", + "year": "2018", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Nutmeg (voice)", + "title": "Isle of Dogs", + "year": "2018", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Motoko Kusanagi (archive footage)", + "title": "Walking to Nam Kok Hotel", + "year": "2018", + "type": "cast" + } + ] + }, + { + "year": "2019", + "credits": [ + { + "mediaType": "movie", + "character": "Nicole Barber", + "title": "Marriage Story", + "year": "2019", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Rosie", + "title": "Jojo Rabbit", + "year": "2019", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Natasha Romanoff / Black Widow (uncredited)", + "title": "Captain Marvel", + "year": "2019", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Self", + "title": "Marriage Story: From the Pages to the Performances", + "year": "2019", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Natasha Romanoff / Black Widow", + "title": "Avengers: Endgame", + "year": "2019", + "type": "cast" + }, + { + "mediaType": "movie", + "character": "Natasha Romanoff / Black Widow (archive footage)", + "title": "Celebrating Marvel's Stan Lee", + "year": "2019", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "The Kelly Clarkson Show", + "episodeCount": 1, + "year": "2019", + "type": "cast" + } + ] + }, + { + "year": "1986", + "credits": [ + { + "mediaType": "tv", + "character": "Self", + "title": "American Masters", + "episodeCount": 1, + "year": "1986", + "type": "cast" + } + ] + }, + { + "year": "1984", + "credits": [ + { + "mediaType": "tv", + "character": "Self", + "title": "Cinema 3", + "episodeCount": 1, + "year": "1984", + "type": "cast" + } + ] + }, + { + "year": "2000", + "credits": [ + { + "mediaType": "tv", + "character": "Self", + "title": "The Queen Latifah Show", + "episodeCount": 1, + "year": "2000", + "type": "cast" + } + ] + }, + { + "year": "1992", + "credits": [ + { + "mediaType": "tv", + "character": "Self", + "title": "Gomorron", + "episodeCount": 2, + "year": "1992", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self - Presenter", + "title": "MTV Movie & TV Awards", + "episodeCount": 3, + "year": "1992", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "The Tonight Show with Jay Leno", + "episodeCount": 8, + "year": "1992", + "type": "cast" + } + ] + }, + { + "year": "1956", + "credits": [ + { + "mediaType": "tv", + "character": "Self", + "title": "Tony Awards", + "episodeCount": 3, + "year": "1956", + "type": "cast" + } + ] + }, + { + "year": "1993", + "credits": [ + { + "mediaType": "tv", + "character": "Self - Guest", + "title": "Late Night with Conan O'Brien", + "episodeCount": 1, + "year": "1993", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Late Show with David Letterman", + "episodeCount": 4, + "year": "1993", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self", + "title": "Good Day L.A.", + "episodeCount": 1, + "year": "1993", + "type": "cast" + } + ] + }, + { + "year": "1988", + "credits": [ + { + "mediaType": "tv", + "character": "Self", + "title": "LIVE with Kelly and Mark", + "episodeCount": 6, + "year": "1988", + "type": "cast" + } + ] + }, + { + "year": "1991", + "credits": [ + { + "mediaType": "tv", + "character": "Self", + "title": "Días de cine", + "episodeCount": 1, + "year": "1991", + "type": "cast" + } + ] + }, + { + "year": "1973", + "credits": [ + { + "mediaType": "tv", + "character": "Herself", + "title": "Fantástico", + "episodeCount": 0, + "year": "1973", + "type": "cast" + } + ] + }, + { + "year": "1987", + "credits": [ + { + "mediaType": "tv", + "character": "Self", + "title": "Biography", + "episodeCount": 1, + "year": "1987", + "type": "cast" + } + ] + }, + { + "year": "1952", + "credits": [ + { + "mediaType": "tv", + "character": "Self", + "title": "Today", + "episodeCount": 4, + "year": "1952", + "type": "cast" + } + ] + }, + { + "year": "1979", + "credits": [ + { + "mediaType": "tv", + "character": "Self", + "title": "CBS News Sunday Morning", + "episodeCount": 1, + "year": "1979", + "type": "cast" + } + ] + }, + { + "year": "1977", + "credits": [ + { + "mediaType": "tv", + "character": "Self", + "title": "Brit Awards", + "episodeCount": 2, + "year": "1977", + "type": "cast" + } + ] + }, + { + "year": "1953", + "credits": [ + { + "mediaType": "tv", + "character": "Self", + "title": "The Oscars", + "episodeCount": 5, + "year": "1953", + "type": "cast" + } + ] + }, + { + "year": "1975", + "credits": [ + { + "mediaType": "tv", + "character": "Self - Host", + "title": "Saturday Night Live", + "episodeCount": 6, + "year": "1975", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "Self - Cameo (uncredited)", + "title": "Saturday Night Live", + "episodeCount": 7, + "year": "1975", + "type": "cast" + }, + { + "mediaType": "tv", + "character": "", + "title": "Saturday Night Live", + "episodeCount": 1, + "year": "1975", + "type": "cast" + } + ] + }, + { + "year": "1966", + "credits": [ + { + "mediaType": "tv", + "character": "Self", + "title": "Goldene Kamera Verleihung", + "episodeCount": 1, + "year": "1966", + "type": "cast" + } + ] + }, + { + "year": "1981", + "credits": [ + { + "mediaType": "tv", + "character": "Self", + "title": "Entertainment Tonight", + "episodeCount": 2, + "year": "1981", + "type": "cast" + } + ] + } +] \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 73abcda..0000000 --- a/LICENSE.md +++ /dev/null @@ -1,9 +0,0 @@ -MIT License - -Copyright (c) 2020 Alex Machin - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index e46ecbd..50b3ccf 100644 --- a/README.md +++ b/README.md @@ -1,165 +1,70 @@ -

- TheOpenMovieDB-GraphQL-Service -

+# The Open Movie DB GraphQL NestJS Example -

- :construction:   TheOpenMovieDB-GraphQL-Example 🚀   In progress.. :construction: -

+

+ Nest Logo +

-

- - Github Dependencies Status - +

+ NPM Version + Package License + CI Status + Coverage +

- - Github Dev Dependencies Status - +A GraphQL API built with NestJS that wraps [The Movie Database (TMDB) API](https://developer.themoviedb.org/docs) to provide movie and TV show data through a GraphQL interface. - - Last commit - +## Features - - MIT License - +- GraphQL API built with NestJS and Apollo Server +- Integration with TMDB API for movie and TV show data +- Type-safe GraphQL schema with TypeScript +- Environment configuration with validation +- Modular architecture following NestJS best practices +- Comprehensive test coverage with Jest +- Code quality tools (ESLint, Prettier) +- CI/CD pipeline with GitHub Actions - Integration and Unit Test Status +## Prerequisites - Linting status -

+- Node.js (v18+) +- Yarn package manager +- TMDB API key (Get one [here](https://developer.themoviedb.org/docs)) -This project contains of all the source code for my current personal project, The Open Movie DB clone. +## Installation -**This project will only contain the Back-end code, the front-end code will be stored in a seperate reposity which can be found [here](https://github.com/AlexMachin1997/TheOpenMovieDB-React-Example)** +1. Install Node.js (v18 or higher) from [nodejs.org](https://nodejs.org/) -## README contents +2. Install Yarn package manager (v4): -As part of the README file it will describe and explain the following sections: + ```bash + corepack enable + corepack prepare yarn@4.1.1 --activate + ``` -- Purpose -- Core features -- Technologies used -- Getting started -- Testing -- Reporting issues -- Feature requests -- Project information +3. Generate an API token from [TMDB API](https://developer.themoviedb.org/docs/getting-started) -## Purpose +4. Clone the repository: -The TheOpenMovieDB-Clone-Graphql-Service is a simple GraphQL service which allows client-side applications to search for movies, tv shows, celebrities. This is also possible thanks to [TheOpenMovieDB](https://developers.themoviedb.org/3) public facing RESTful API interface + ```bash + git clone https://github.com/AlexMachin1997/TheOpenMovieDB-GraphQL-Example.git + cd TheOpenMovieDB-GraphQL-Example + ``` - - Example of The Open Movie Database Web App - +5. Install dependencies: -While most of the heavy will work will done by their API it will be this API's job to ensure all the data is optimized and transformed into a useable format for the client-sides state management library, [apollo-client](https://www.apollographql.com/docs/react/). + ```bash + yarn install + ``` -## Core features +6. Create a `.env` file in the root directory and add your TMDB API token: -As of the current version of the GraphQL service, it is currently capable of performing the following tasks: + ``` + TMDB_API_TOKEN=your_api_token_here + ``` -- Search for Movies, TV Shows, People +7. Start the development server: + ```bash + yarn start:dev + ``` -- Search for a Movies, Person and Show - -- Search for a movies and shows reviews, cast, crew, reccomendations, keywords, social and videos - -- Discover films and shows - -- Search the most popular movies, shows and people - -- Search for movies and shows which are currently playing - -- Search for upcoming movies and shows - -- Search for top rated movies and shows - -- Filter for credits, this is an indvidual endpoint for filtering through a persons credits by categories ike acting, writing, producing etc - -- Provide a cache via the apollo-server caching mechanism, it will currently cache for 1 hour -- Generate the endpoints based on the config provided through the env variables - -- Providing various utils for certain situations e.g. convering values to percentages, replacing object keys, generating custom dates etc - -- Clear and easy to understand documention which is automatically generated by the apollo-server, it inteligently knwons all the schemas and resolvers - -## Technologies used: - -### Core Dependencies - -- [apollo-server](https://www.npmjs.com/package/apollo-server) -- [axios](https://www.npmjs.com/package/axios) -- [dotenv](https://www.npmjs.com/package/dotenv) -- [lodash](https://www.npmjs.com/package/lodash) -- [moment](https://www.npmjs.com/package/moment) -- [nodemon](https://www.npmjs.com/package/nodemon) - -### Development Dependencies - -- [eslint](https://www.npmjs.com/package/eslint) -- [eslint-config-airbnb-base](https://www.npmjs.com/package/eslint-config-airbnb-base) -- [eslint-config-prettier](https://www.npmjs.com/package/eslint-config-prettier) -- [eslint-plugin-import](https://www.npmjs.com/package/eslint-plugin-import) -- [eslint-plugin-prettier](https://www.npmjs.com/package/eslint-plugin-prettier) -- [jest](https://www.npmjs.com/package/jest) -- [prettier](https://www.npmjs.com/package/prettier) - -## Getting started - -- Clone the project to your development environment by using `git clone https://github.com/AlexMachin1997/TheOpenMovieDB-Graphql-Example.git` - -- Install all dependencies for the application by issuing this command `npm run coreDependencies` **(Excludes all development dependencies e.g. prettier, eslint, jest etc)** - -## Unit and integration testing setup - -To get started with the testing suites you will need to install all the development dependencies used for the application. To install the dependencies use `npm run devDependencies`, this will install all the development dependencies. By choosing install all the development dependencies you will be able to run unit and integration tests aswell as linting and prettifying the applications code. - -The Applications tests - -- GraphQL resolver mocking **(COMING SOON)** - -- Checks all the math utils work as exepected e.g. outputting numbers into percentages - -- Checks all the the date utilites work as expected e.g. outputting a celebrites birthday - -- Checks images can generate an absolute image url e.g. https://image.tmdb.org/t/p/w440_and_h660_face/q3E71oY6qgAEiw6YZIHDlHSLwer.jpg - -- Checks the "Discover" endpoints query parameters can be generated e.g. ?search="Iron Man" - -- Checks all the object utils work as expected e.g. updating object keys - -- Checks all the resolver specific utils work as expected e.g. sorting data - -## Contributing - -### Reporting issues - -If you find any problems while using the API, report them [here](https://github.com/AlexMachin1997/TheOpenMovieDB-Graphql-Example/issues), and I will address them as quick as I can. - -### Feature requests - -If you would like to request features for future versions of the application again, please post them [here](https://github.com/AlexMachin1997/TheOpenMovieDB-Graphql-Example/issues). When posting ideas ensure the functionality is explained to provide any developers contributing to the project know what to implement. - -### Implimenting features - -If you would like to impliment a feature in the issues list or refactor existing code (Withut breaking exisitng functionality), feel free to form the repo and submit a [PR](https://github.com/AlexMachin1997/TheOpenMovieDB-Graphql-Example/pulls) detailing all the changes mades. - -# Project Information - -### Author information - -Alex Machin - -If you want to connect with me on my professional social network platforms feel free to use the links located below, but please don't abuse them. - -- [LinkedIn](https://www.linkedin.com/in/alex-machin/) -- [Twitter](https://twitter.com/AlexMachin97) - -### Application Versioning - -The application is currently at version 1.2.0, with each feature added it will increment based on these [guidelines](https://docs.npmjs.com/about-semantic-versioning) - -### Project Licence information - -This project is licensed under the MIT License, for more details about the PWA refer to the LICENSE.md file located within the project. +The GraphQL playground will be available at http://localhost:3000/graphql diff --git a/config.js b/config.js deleted file mode 100644 index 2ec71d0..0000000 --- a/config.js +++ /dev/null @@ -1,14 +0,0 @@ -// The Open Movie Database URL -const API_URI = process.env.OPEN_MOVIE_DB_API_URI; - -// The Open Movie Database URL Version -const API_VERSION = 3; - -// The Open Movie Database API Key -const API_KEY = process.env.OPEN_MOVIE_DB_API_KEY; - -module.exports = { - API_URI, - API_VERSION, - API_KEY -}; diff --git a/index.js b/index.js deleted file mode 100644 index be0bb33..0000000 --- a/index.js +++ /dev/null @@ -1,18 +0,0 @@ -// @ts-nocheck -require('dotenv').config(); - -const { ApolloServer } = require('apollo-server'); -const { ApolloServerPluginCacheControl } = require('apollo-server-core'); - -const { schema } = require('./schema'); - -// Create the ApolloServer instance and pass in the transformed GraphQL schemas (Models) and resolvers (Controllers) -const server = new ApolloServer({ - schema, - plugins: [ApolloServerPluginCacheControl({ defaultMaxAge: 3600000 })] // 1 hour -}); - -// Start listening to the ApolloServer for GraphQL queries like DiscoverMovies(relaseDate: 2019) -server.listen().then(({ url }) => { - console.log(`🚀 Server ready at ${url}`); -}); diff --git a/jsconfig.json b/jsconfig.json deleted file mode 100644 index e6bfae7..0000000 --- a/jsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "compilerOptions": { - "checkJs": true, - "target": "es5" - }, - "exclude": ["node_modules", "**/node_modules/*"] -} diff --git a/models/BelongsToCollection/index.js b/models/BelongsToCollection/index.js deleted file mode 100644 index 342c39e..0000000 --- a/models/BelongsToCollection/index.js +++ /dev/null @@ -1,12 +0,0 @@ -const { gql } = require('apollo-server'); - -const typeDef = gql` - type BelowsToCollection { - id: Int - name: String - backgroundUrl: String - posterUrl: String - } -`; - -module.exports = typeDef; diff --git a/models/Cast/index.js b/models/Cast/index.js deleted file mode 100644 index 8a2a5be..0000000 --- a/models/Cast/index.js +++ /dev/null @@ -1,13 +0,0 @@ -const { gql } = require('apollo-server'); - -const typeDef = gql` - type Cast { - id: Int - character: String - profileImageUrl: String - gender: String - episodeCount: Int - } -`; - -module.exports = typeDef; diff --git a/models/Company/index.js b/models/Company/index.js deleted file mode 100644 index 553d427..0000000 --- a/models/Company/index.js +++ /dev/null @@ -1,11 +0,0 @@ -const { gql } = require('apollo-server'); - -const typeDef = gql` - type Company { - id: Int - logo: String - name: String - } -`; - -module.exports = typeDef; diff --git a/models/Crew/index.js b/models/Crew/index.js deleted file mode 100644 index 9fec7d5..0000000 --- a/models/Crew/index.js +++ /dev/null @@ -1,10 +0,0 @@ -const { gql } = require('apollo-server'); - -const typeDef = gql` - type Crew { - name: String - roles: String - } -`; - -module.exports = typeDef; diff --git a/models/Genres/index.js b/models/Genres/index.js deleted file mode 100644 index f4ec92a..0000000 --- a/models/Genres/index.js +++ /dev/null @@ -1,10 +0,0 @@ -const { gql } = require('apollo-server'); - -const typeDef = gql` - type Genre { - id: Int - name: String - } -`; - -module.exports = typeDef; diff --git a/models/Keyword/index.js b/models/Keyword/index.js deleted file mode 100644 index 966acd4..0000000 --- a/models/Keyword/index.js +++ /dev/null @@ -1,10 +0,0 @@ -const { gql } = require('apollo-server'); - -const typeDef = gql` - type Keyword { - id: String - name: String - } -`; - -module.exports = typeDef; diff --git a/models/Movie/Movies.js b/models/Movie/Movies.js deleted file mode 100644 index 7d84cbb..0000000 --- a/models/Movie/Movies.js +++ /dev/null @@ -1,17 +0,0 @@ -const { gql } = require('apollo-server'); - -const MoviesModel = gql` - type Movies { - id: Int - name: String - overview: String - backgroundUrl: String - posterUrl: String - genres: [Int] - releaseDate: String - originalLanguage: String - voteAverage: Float - } -`; - -module.exports = MoviesModel; diff --git a/models/Movie/index.js b/models/Movie/index.js deleted file mode 100644 index a40b2c1..0000000 --- a/models/Movie/index.js +++ /dev/null @@ -1,33 +0,0 @@ -const { gql } = require('apollo-server'); - -const MovieModel = gql` - type Movie { - id: Int - name: String - overview: String - backgroundUrl: String - posterUrl: String - genres: [Genre] - homepage: String - originalLanguage: String - productionCompanies: [Company] - releaseDate: String - voteAverage: Float - status: String - reviews: [Review] - recommendations: [Movie] - keywords: [Keyword] - social: Social - featuredCast: [Cast] - featuredCrew: [Crew] - featuredVideo: Video - belongsToCollection: BelowsToCollection - tagline: String - runtime: String - - budget: String - revenue: String - } -`; - -module.exports = MovieModel; diff --git a/models/People/Credits.js b/models/People/Credits.js deleted file mode 100644 index 082e08d..0000000 --- a/models/People/Credits.js +++ /dev/null @@ -1,18 +0,0 @@ -const { gql } = require('apollo-server'); - -const typeDef = gql` - type Credits { - year: String - credits: [Credit] - } - - type Credit { - releaseDate: String - title: String - episodeCount: String - mediaType: String - role: String - } -`; - -module.exports = typeDef; diff --git a/models/People/Person.js b/models/People/Person.js deleted file mode 100644 index 5553990..0000000 --- a/models/People/Person.js +++ /dev/null @@ -1,28 +0,0 @@ -const { gql } = require('apollo-server'); - -const typeDef = gql` - type Person { - id: Int - birthday: String - knowForDepartment: String - name: String - alsoKnownAs: [String] - gender: String - overview: String # Biography - placeOfBirth: String - posterUrl: String # profile_path - credits: PeopleCredits - social: Social - homepage: String - } - - type PeopleCredits { - ActingGroup: [Credits] - ProductionGroup: [Credits] - WritingGroup: [Credits] - DirectingGroup: [Credits] - CrewGroup: [Credits] - } -`; - -module.exports = typeDef; diff --git a/models/People/index.js b/models/People/index.js deleted file mode 100644 index d4ccd95..0000000 --- a/models/People/index.js +++ /dev/null @@ -1,12 +0,0 @@ -const { gql } = require('apollo-server'); - -const typeDef = gql` - type People { - id: Int - name: String - posterUrl: String - roles: String - } -`; - -module.exports = typeDef; diff --git a/models/Query/index.js b/models/Query/index.js deleted file mode 100644 index f7b19ad..0000000 --- a/models/Query/index.js +++ /dev/null @@ -1,40 +0,0 @@ -const { gql } = require('apollo-server'); - -const Query = gql` - type Query { - DiscoverMovies( - sortBy: String - genres: String - certifications: String - userscore: String - runtime: String - ): [Movies] - - DiscoverShows( - sortBy: String - genres: String - certifications: String - userscore: String - runtime: String - ): [Shows] - - SearchForAMovie(search: String!, id: Int!): Movie - SearchForAShow(search: String!, id: Int!): Show - SearchForAPerson(search: String!, id: Int!): Person - - PopularMovies: [Movies] - PopularShows: [Shows] - PopularPeople: [People] - - UpcomingShows: [Shows] - UpcomingMovies: [Movies] - - NowPlayingShows: [Shows] - NowPlayingMovies: [Movies] - - TopRatedMovies: [Movies] - TopRatedShows: [Shows] - } -`; - -module.exports = Query; diff --git a/models/Review/index.js b/models/Review/index.js deleted file mode 100644 index 334910a..0000000 --- a/models/Review/index.js +++ /dev/null @@ -1,12 +0,0 @@ -const { gql } = require('apollo-server'); - -const typeDef = gql` - type Review { - author: String - content: String - id: String - url: String - } -`; - -module.exports = typeDef; diff --git a/models/Show/CurrentSeason.js b/models/Show/CurrentSeason.js deleted file mode 100644 index 0adef69..0000000 --- a/models/Show/CurrentSeason.js +++ /dev/null @@ -1,13 +0,0 @@ -const { gql } = require('apollo-server'); - -const typeDef = gql` - type CurrentSeason { - backgroundUrl: String - seasonNumber: Int - year: String - episodeCount: Int - overview: String - } -`; - -module.exports = typeDef; diff --git a/models/Show/Network.js b/models/Show/Network.js deleted file mode 100644 index b55bcb5..0000000 --- a/models/Show/Network.js +++ /dev/null @@ -1,12 +0,0 @@ -const { gql } = require('apollo-server'); - -const typeDef = gql` - type Network { - name: String - id: Int - logoUrl: String - originCountry: String - } -`; - -module.exports = typeDef; diff --git a/models/Show/Shows.js b/models/Show/Shows.js deleted file mode 100644 index ef63dd0..0000000 --- a/models/Show/Shows.js +++ /dev/null @@ -1,17 +0,0 @@ -const { gql } = require('apollo-server'); - -const typeDef = gql` - type Shows { - id: String - name: String - overview: String - backgroundUrl: String - posterUrl: String - genres: [Int] - releaseDate: String - originalLanguage: String - voteAverage: Float - } -`; - -module.exports = typeDef; diff --git a/models/Show/index.js b/models/Show/index.js deleted file mode 100644 index 5699e51..0000000 --- a/models/Show/index.js +++ /dev/null @@ -1,38 +0,0 @@ -const { gql } = require('apollo-server'); - -const typeDef = gql` - type Show { - id: Int - name: String - overview: String - backgroundUrl: String - posterUrl: String - genres: [Genre] - homepage: String - originalLanguage: String - productionCompanies: [Company] - releaseDate: String - voteAverage: Float - status: String - reviews: [Review] - recommendations: [Movie] - keywords: [Keyword] - social: Social - featuredCast: [Cast] - featuredCrew: [Crew] - featuredVideo: Video - belongsToCollection: BelowsToCollection - tagline: String - runtime: String - - Network: [Network] - numberOfSeasons: Int - numberOfEpisodes: Int - originCountry: [String] - company: [Company] - currentSeason: CurrentSeason - type: String - } -`; - -module.exports = typeDef; diff --git a/models/Social/index.js b/models/Social/index.js deleted file mode 100644 index 0a14c4d..0000000 --- a/models/Social/index.js +++ /dev/null @@ -1,12 +0,0 @@ -const { gql } = require('apollo-server'); - -const typeDef = gql` - type Social { - facebook: String - instagram: String - twitter: String - homepage: String - } -`; - -module.exports = typeDef; diff --git a/models/Videos/index.js b/models/Videos/index.js deleted file mode 100644 index 59b6e82..0000000 --- a/models/Videos/index.js +++ /dev/null @@ -1,13 +0,0 @@ -const { gql } = require('apollo-server'); - -const typeDef = gql` - type Video { - id: String - name: String - url: String - type: String - site: String - } -`; - -module.exports = typeDef; diff --git a/nest-cli.json b/nest-cli.json new file mode 100644 index 0000000..f9aa683 --- /dev/null +++ b/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/package.json b/package.json index 254a522..9cb03c5 100644 --- a/package.json +++ b/package.json @@ -1,61 +1,77 @@ { - "name": "theopenmoviedb-graphql-example", - "version": "1.2.0", - "description": "A GraphQL service to handle incoming requests from the apollo-client, it's purpose is to find movies, shows, actors and provide various filtering capabilites", - "main": "index.js", + "name": "the-open-movie-db-graph-ql-nest-js-example", + "version": "0.0.1", + "description": "", + "author": "Alex Machin", + "private": true, + "license": "UNLICENSED", "scripts": { - "test": "jest", - "start": "node index.js", - "server": "nodemon index.js", - "lint": "eslint --fix . --ext .js", - "allDependencies": "yarn run coreDependencies && yarn run devDependencies", - "coreDependencies": "yarn install", - "devDependencies": " yarn install --dev" - }, - "jest": { - "setupFiles": [ - "dotenv/config" - ] + "build": "nest build", + "lint": "yarn run eslint ./src/**/*.{ts,tsx} --fix", + "format": "prettier --write ./src", + "ts-validate": "tsc", + "validate": "yarn run ts-validate && yarn run format && yarn run lint", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "test": "vitest", + "test:watch": "vitest --watch", + "test:cov": "vitest --coverage", + "test:debug": "vitest --inspect", + "test:e2e": "vitest --config ./test/vitest-e2e.config.ts", + "prepare": "husky install", + "gql-to-interfaces": "cd ./src/scripts && ts-node schema-to-typings.ts" }, - "keywords": [ - "Graphql", - "Apollo", - "Lodash", - "Dotenv", - "Apollo-Server", - "MomentJS", - "Nodemon", - "NPM" - ], - "author": "Alex Machin", - "license": "MIT", "dependencies": { - "@graphql-tools/schema": "^8.3.1", - "apollo-server": "^2.17.0", - "apollo-server-core": "^3.5.0", - "axios": "^0.19.2", - "dotenv": "^8.2.0", - "graphql": "15.7.2", - "lodash": "^4.17.20", - "moment": "^2.28.0", - "nodemon": "^2.0.3" + "@apollo/server": "^4.10.4", + "@nestjs/apollo": "^12.1.0", + "@nestjs/axios": "^3.0.2", + "@nestjs/common": "^10.0.0", + "@nestjs/config": "^3.2.3", + "@nestjs/core": "^10.0.0", + "@nestjs/graphql": "^12.1.1", + "@nestjs/mapped-types": "*", + "@nestjs/platform-express": "^10.0.0", + "@swc/core": "^1.7.40", + "@vitest/coverage-v8": "^2.1.4", + "@vitest/eslint-plugin": "^1.1.7", + "axios": "^1.7.2", + "date-fns": "^3.6.0", + "eslint-plugin-vitest": "^0.5.4", + "graphql": "^16.8.1", + "joi": "^17.13.3", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.8.1", + "ts-morph": "^22.0.0", + "unplugin-swc": "^1.5.1", + "vitest": "^2.1.4", + "zod": "^3.23.8" }, "devDependencies": { - "@types/jest": "^26.0.14", - "eslint": "^7.9.0", - "eslint-config-airbnb-base": "^14.2.0", - "eslint-config-prettier": "^6.11.0", - "eslint-plugin-import": "^2.22.0", - "eslint-plugin-prettier": "^3.1.4", - "jest": "^26.4.2", - "prettier": "^2.1.2" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/AlexMachin1997/TheOpenMovieDB-Graphql-Example.git" - }, - "bugs": { - "url": "https://github.com/AlexMachin1997/TheOpenMovieDB-Graphql-Example/issues" + "@nestjs/cli": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/express": "^4.17.17", + "@types/node": "^20.3.1", + "@types/supertest": "^2.0.12", + "@typescript-eslint/eslint-plugin": "^8.12.2", + "@typescript-eslint/parser": "^8.12.2", + "eslint": "^8.56.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.1.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-prettier": "^5.1.2", + "eslint-plugin-sonarjs": "^0.23.0", + "husky": "^8.0.0", + "prettier": "^3.0.0", + "source-map-support": "^0.5.21", + "supertest": "^6.3.3", + "ts-loader": "^9.4.3", + "ts-node": "^10.9.1", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.1.3" }, - "homepage": "https://github.com/AlexMachin1997/TheOpenMovieDB-Graphql-Example#readme" + "packageManager": "yarn@4.2.2" } diff --git a/resolvers/Cast/MovieResolver.js b/resolvers/Cast/MovieResolver.js deleted file mode 100644 index 4441240..0000000 --- a/resolvers/Cast/MovieResolver.js +++ /dev/null @@ -1,23 +0,0 @@ -const axios = require('axios'); - -const generateCastURLEndpoint = require('../../utils/generateEndpoints/Cast'); -const setCast = require('../../utils/resolverUtils/Cast/setCast'); - -// eslint-disable-next-line no-unused-vars -const MovieCastResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generateCastURLEndpoint(parent.id, 'movie')); - - const { data } = response; - const { cast } = data; - - const Cast = setCast(cast); - - return Cast; - } catch (err) { - console.log('The /credits (Cast) endpoint failed'); - return err.response; - } -}; - -module.exports = MovieCastResolver; diff --git a/resolvers/Cast/ShowResolver.js b/resolvers/Cast/ShowResolver.js deleted file mode 100644 index e72a20e..0000000 --- a/resolvers/Cast/ShowResolver.js +++ /dev/null @@ -1,23 +0,0 @@ -const axios = require('axios'); - -const generateCastURLEndpoint = require('../../utils/generateEndpoints/Cast'); -const setCast = require('../../utils/resolverUtils/Cast/setCast'); - -// eslint-disable-next-line no-unused-vars -const TVCastResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generateCastURLEndpoint(parent.id, 'tv')); - - const { data } = response; - const { cast } = data; - - const Cast = await setCast(cast, 'tv'); - - return Cast; - } catch (err) { - console.log('The /credits (Cast) endpoint failed'); - return err.response; - } -}; - -module.exports = TVCastResolver; diff --git a/resolvers/Cast/index.js b/resolvers/Cast/index.js deleted file mode 100644 index bb3edf0..0000000 --- a/resolvers/Cast/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const MovieCastResolver = require('./MovieResolver'); -const ShowCastResolver = require('./ShowResolver'); - -module.exports = { - MovieCastResolver, - ShowCastResolver -}; diff --git a/resolvers/Credits/Credits.js b/resolvers/Credits/Credits.js deleted file mode 100644 index 845c015..0000000 --- a/resolvers/Credits/Credits.js +++ /dev/null @@ -1,28 +0,0 @@ -const axios = require('axios'); - -const generatePersonCreditsEndpoint = require('../../utils/generateEndpoints/Credits'); -const setCredits = require('../../utils/resolverUtils/Credits/setCredits'); - -// eslint-disable-next-line no-unused-vars -const CreditsResolver = async (parent, args, info, context) => { - try { - const response = await axios.get(generatePersonCreditsEndpoint(parent.id)); - - const { ActingGroup, ProductionGroup, WritingGroup, DirectingGroup, CrewGroup } = setCredits( - response.data - ); - - return { - ActingGroup, - ProductionGroup, - WritingGroup, - DirectingGroup, - CrewGroup - }; - } catch (err) { - console.log(err); - return err.response; - } -}; - -module.exports = CreditsResolver; diff --git a/resolvers/Credits/index.js b/resolvers/Credits/index.js deleted file mode 100644 index 0d85ea4..0000000 --- a/resolvers/Credits/index.js +++ /dev/null @@ -1,5 +0,0 @@ -const CreditsResolver = require('./Credits'); - -module.exports = { - CreditsResolver -}; diff --git a/resolvers/Crew/MovieResolver.js b/resolvers/Crew/MovieResolver.js deleted file mode 100644 index 2f1141a..0000000 --- a/resolvers/Crew/MovieResolver.js +++ /dev/null @@ -1,23 +0,0 @@ -const axios = require('axios'); - -const generateCrewEndpoint = require('../../utils/generateEndpoints/Crew'); -const setCrew = require('../../utils/resolverUtils/Crew/setCrew'); - -// eslint-disable-next-line no-unused-vars -const MovieCrewResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generateCrewEndpoint(parent.id, 'movie')); - - const { data } = response; - const { crew } = data; - - const Crew = setCrew(crew); - - return Crew; - } catch (err) { - console.log('The /credits (Crew) endpoint failed'); - return err; - } -}; - -module.exports = MovieCrewResolver; diff --git a/resolvers/Crew/ShowResolver.js b/resolvers/Crew/ShowResolver.js deleted file mode 100644 index 778b6b1..0000000 --- a/resolvers/Crew/ShowResolver.js +++ /dev/null @@ -1,23 +0,0 @@ -const axios = require('axios'); - -const generateCrewEndpoint = require('../../utils/generateEndpoints/Crew'); -const setCrew = require('../../utils/resolverUtils/Crew/setCrew'); - -// eslint-disable-next-line no-unused-vars -const TVCrewResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generateCrewEndpoint(parent.id, 'tv')); - - const { data } = response; - const { crew } = data; - - const Crew = setCrew(crew); - - return Crew; - } catch (err) { - console.log('The /credits (Crew) endpoint failed'); - return err; - } -}; - -module.exports = TVCrewResolver; diff --git a/resolvers/Crew/index.js b/resolvers/Crew/index.js deleted file mode 100644 index 205b3f8..0000000 --- a/resolvers/Crew/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const MovieCrewResolver = require('./MovieResolver'); -const ShowCrewResolver = require('./ShowResolver'); - -module.exports = { - MovieCrewResolver, - ShowCrewResolver -}; diff --git a/resolvers/Discover/MoviesResolver.js b/resolvers/Discover/MoviesResolver.js deleted file mode 100644 index a522983..0000000 --- a/resolvers/Discover/MoviesResolver.js +++ /dev/null @@ -1,27 +0,0 @@ -const axios = require('axios'); - -const generateDiscoverEndpoint = require('../../utils/generateEndpoints/Discover'); -const generateQueryParameters = require('../../utils/generateQueryParameters/Discover'); - -const setMovies = require('../../utils/resolverUtils/Movies/setMovies'); - -// eslint-disable-next-line no-unused-vars -const DiscoverMoviesResolver = async (parent, args, context, info) => { - try { - const response = await axios.get( - generateQueryParameters(generateDiscoverEndpoint('movie'), args) - ); - - const { data } = response; - const { results } = data; - - const Movies = setMovies(results); - - return Movies; - } catch (err) { - console.log('The /Discover/Movie endpoint failed'); - return err.response; - } -}; - -module.exports = DiscoverMoviesResolver; diff --git a/resolvers/Discover/ShowResolver.js b/resolvers/Discover/ShowResolver.js deleted file mode 100644 index 068be83..0000000 --- a/resolvers/Discover/ShowResolver.js +++ /dev/null @@ -1,25 +0,0 @@ -const axios = require('axios'); - -const generateDiscoverEndpoint = require('../../utils/generateEndpoints/Discover'); -const generateQueryParameters = require('../../utils/generateQueryParameters/Discover'); -const setShows = require('../../utils/resolverUtils/Shows/setShows'); - -// eslint-disable-next-line no-unused-vars -const DiscoverTVResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generateQueryParameters(generateDiscoverEndpoint('tv'), args)); - - const { data } = response; - const { results } = data; - - const Shows = setShows(results); - - return Shows; - } catch (err) { - console.log(err); - console.log('The /Discover/TV endpoint failed'); - return err.response; - } -}; - -module.exports = DiscoverTVResolver; diff --git a/resolvers/Discover/index.js b/resolvers/Discover/index.js deleted file mode 100644 index a8bfe75..0000000 --- a/resolvers/Discover/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const DiscoverMoviesResolver = require('./MoviesResolver'); -const DiscoverShowsResolver = require('./ShowResolver'); - -module.exports = { - DiscoverMoviesResolver, - DiscoverShowsResolver -}; diff --git a/resolvers/Keywords/MovieResolver.js b/resolvers/Keywords/MovieResolver.js deleted file mode 100644 index e44ff92..0000000 --- a/resolvers/Keywords/MovieResolver.js +++ /dev/null @@ -1,18 +0,0 @@ -const axios = require('axios'); - -const generateKeywordEndpoint = require('../../utils/generateEndpoints/Keywords'); - -// eslint-disable-next-line no-unused-vars -const MovieKeywordResolver = async (parent, args, info, context) => { - try { - // Make a keywords request using the Movie object id field - const response = await axios.get(generateKeywordEndpoint(parent.id, 'movie')); - - return response.data.keywords; - } catch (err) { - console.log('The /keywords endpoint failed'); - return err; - } -}; - -module.exports = MovieKeywordResolver; diff --git a/resolvers/Keywords/ShowResolver.js b/resolvers/Keywords/ShowResolver.js deleted file mode 100644 index 0ee9901..0000000 --- a/resolvers/Keywords/ShowResolver.js +++ /dev/null @@ -1,17 +0,0 @@ -const axios = require('axios'); - -const generateKeywordEndpoint = require('../../utils/generateEndpoints/Keywords'); - -// eslint-disable-next-line no-unused-vars -const TVKeywordResolver = async (parent, args, info, context) => { - try { - // Make a keywords request using the TV object id field - const response = await axios.get(generateKeywordEndpoint(parent.id, 'tv')); - return response.data.results; - } catch (err) { - console.log('The /keywords endpoint failed'); - return err; - } -}; - -module.exports = TVKeywordResolver; diff --git a/resolvers/Keywords/index.js b/resolvers/Keywords/index.js deleted file mode 100644 index 49ee4cf..0000000 --- a/resolvers/Keywords/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const MovieKeywordResolver = require('./MovieResolver'); -const ShowKeywordResolver = require('./ShowResolver'); - -module.exports = { - MovieKeywordResolver, - ShowKeywordResolver -}; diff --git a/resolvers/NowPlaying/MoviesResolver.js b/resolvers/NowPlaying/MoviesResolver.js deleted file mode 100644 index 396f15b..0000000 --- a/resolvers/NowPlaying/MoviesResolver.js +++ /dev/null @@ -1,23 +0,0 @@ -const axios = require('axios'); - -const generateNowPlayingEndpoint = require('../../utils/generateEndpoints/NowPlaying'); -const setMovies = require('../../utils/resolverUtils/Movies/setMovies'); - -// eslint-disable-next-line no-unused-vars -const NowPlayingMovieResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generateNowPlayingEndpoint('movie')); - - const { data } = response; - const { results } = data; - - const Movies = setMovies(results); - - return Movies; - } catch (err) { - console.log('The tv/on_the_air endpoint failed'); - return err.response; - } -}; - -module.exports = NowPlayingMovieResolver; diff --git a/resolvers/NowPlaying/ShowResolver.js b/resolvers/NowPlaying/ShowResolver.js deleted file mode 100644 index 82a4935..0000000 --- a/resolvers/NowPlaying/ShowResolver.js +++ /dev/null @@ -1,23 +0,0 @@ -const axios = require('axios'); - -const generateNowPlayingEndpoint = require('../../utils/generateEndpoints/NowPlaying'); -const setShows = require('../../utils/resolverUtils/Shows/setShows'); - -// eslint-disable-next-line no-unused-vars -const NowPlayingTVResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generateNowPlayingEndpoint('tv')); - - const { data } = response; - const { results } = data; - - const Shows = setShows(results); - - return Shows; - } catch (err) { - console.log('The tv/on_the_air endpoint failed'); - return err.response; - } -}; - -module.exports = NowPlayingTVResolver; diff --git a/resolvers/NowPlaying/index.js b/resolvers/NowPlaying/index.js deleted file mode 100644 index cc50a23..0000000 --- a/resolvers/NowPlaying/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const NowPlayingShowsResolver = require('./ShowResolver'); -const NowPlayingMovieResolver = require('./MoviesResolver'); - -module.exports = { - NowPlayingMovieResolver, - NowPlayingShowsResolver -}; diff --git a/resolvers/Popular/MovieResolver.js b/resolvers/Popular/MovieResolver.js deleted file mode 100644 index 556f6e9..0000000 --- a/resolvers/Popular/MovieResolver.js +++ /dev/null @@ -1,23 +0,0 @@ -const axios = require('axios'); - -const generatePopularEndpoint = require('../../utils/generateEndpoints/Popular'); -const setMovies = require('../../utils/resolverUtils/Movies/setMovies'); - -// eslint-disable-next-line no-unused-vars -const MoviePopularResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generatePopularEndpoint('movie')); - - const { data } = response; - const { results } = data; - - const Movies = setMovies(results); - - return Movies; - } catch (err) { - console.log('The /movie/popular endpoint failed'); - return err.response; - } -}; - -module.exports = MoviePopularResolver; diff --git a/resolvers/Popular/PeopleResolver.js b/resolvers/Popular/PeopleResolver.js deleted file mode 100644 index 5441495..0000000 --- a/resolvers/Popular/PeopleResolver.js +++ /dev/null @@ -1,26 +0,0 @@ -const axios = require('axios'); - -const generatePopularEndpoint = require('../../utils/generateEndpoints/Popular'); -const setPeople = require('../../utils/resolverUtils/People/setPeople'); - -// eslint-disable-next-line no-unused-vars -const PopularPeopleResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generatePopularEndpoint('person')); - - const { data } = response; - const { results } = data; - - const People = setPeople(results); - - return People; - - // return response.data.results; - } catch (err) { - console.log(err); - console.log('The /tv/popular endpoint failed'); - return err.response; - } -}; - -module.exports = PopularPeopleResolver; diff --git a/resolvers/Popular/ShowResolver.js b/resolvers/Popular/ShowResolver.js deleted file mode 100644 index 4f0be5e..0000000 --- a/resolvers/Popular/ShowResolver.js +++ /dev/null @@ -1,23 +0,0 @@ -const axios = require('axios'); - -const generatePopularEndpoint = require('../../utils/generateEndpoints/Popular'); -const setShows = require('../../utils/resolverUtils/Shows/setShows'); - -// eslint-disable-next-line no-unused-vars -const TVPopularResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generatePopularEndpoint('tv')); - - const { data } = response; - const { results } = data; - - const Shows = setShows(results); - - return Shows; - } catch (err) { - console.log('The /tv/popular endpoint failed'); - return err.response; - } -}; - -module.exports = TVPopularResolver; diff --git a/resolvers/Popular/index.js b/resolvers/Popular/index.js deleted file mode 100644 index 2c0de4e..0000000 --- a/resolvers/Popular/index.js +++ /dev/null @@ -1,9 +0,0 @@ -const PopularMoviesResolver = require('./MovieResolver'); -const PopularShowsResolver = require('./ShowResolver'); -const PopularPeopleResolver = require('./PeopleResolver'); - -module.exports = { - PopularMoviesResolver, - PopularShowsResolver, - PopularPeopleResolver -}; diff --git a/resolvers/Recommendations/MovieResolver.js b/resolvers/Recommendations/MovieResolver.js deleted file mode 100644 index e4b365a..0000000 --- a/resolvers/Recommendations/MovieResolver.js +++ /dev/null @@ -1,24 +0,0 @@ -const axios = require('axios'); - -const generateRecommendationsEndpoint = require('../../utils/generateEndpoints/Recommendations'); -const setMovies = require('../../utils/resolverUtils/Movies/setMovies'); - -// eslint-disable-next-line no-unused-vars -const MovieRecommendationsResolver = async (parent, args, content, info) => { - try { - const response = await axios.get(generateRecommendationsEndpoint(parent.id, 'movie')); - - const { data } = response; - - const { results } = data; - - const Movies = setMovies(results); - - return Movies; - } catch (err) { - console.log('The /movie/recommendations/ endpoint failed'); - return err; - } -}; - -module.exports = MovieRecommendationsResolver; diff --git a/resolvers/Recommendations/ShowResolver.js b/resolvers/Recommendations/ShowResolver.js deleted file mode 100644 index 4a2d3b6..0000000 --- a/resolvers/Recommendations/ShowResolver.js +++ /dev/null @@ -1,23 +0,0 @@ -const axios = require('axios'); - -const generateRecommendationsEndpoint = require('../../utils/generateEndpoints/Recommendations'); -const setShows = require('../../utils/resolverUtils/Shows/setShows'); - -// eslint-disable-next-line no-unused-vars -const TVRecommendationsResolver = async (parent, args, content, info) => { - try { - const response = await axios.get(generateRecommendationsEndpoint(parent.id, 'tv')); - - const { data } = response; - const { results } = data; - - const Shows = setShows(results); - - return Shows; - } catch (err) { - console.log('The /tv/recommendations/ endpoint failed'); - return err; - } -}; - -module.exports = TVRecommendationsResolver; diff --git a/resolvers/Recommendations/index.js b/resolvers/Recommendations/index.js deleted file mode 100644 index 1a01c4f..0000000 --- a/resolvers/Recommendations/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const MovieRecommendationsResolver = require('./MovieResolver'); -const ShowRecommendationsResolver = require('./ShowResolver'); - -module.exports = { - MovieRecommendationsResolver, - ShowRecommendationsResolver -}; diff --git a/resolvers/Reviews/MovieResolver.js b/resolvers/Reviews/MovieResolver.js deleted file mode 100644 index 3264f7b..0000000 --- a/resolvers/Reviews/MovieResolver.js +++ /dev/null @@ -1,18 +0,0 @@ -const axios = require('axios'); - -const generateReviewEndpoint = require('../../utils/generateEndpoints/Reviews'); - -// eslint-disable-next-line no-unused-vars -const MovieReviewsResolver = async (parent, args, context, info) => { - try { - // Make a review request using the Movie object id field - const response = await axios.get(generateReviewEndpoint(parent.id, 'movie')); - - return response.data.results; - } catch (err) { - console.log('The /reviews endpoint failed'); - return err.response; - } -}; - -module.exports = MovieReviewsResolver; diff --git a/resolvers/Reviews/ShowResolver.js b/resolvers/Reviews/ShowResolver.js deleted file mode 100644 index d4cb393..0000000 --- a/resolvers/Reviews/ShowResolver.js +++ /dev/null @@ -1,18 +0,0 @@ -const axios = require('axios'); - -const generateReviewEndpoint = require('../../utils/generateEndpoints/Reviews'); - -// eslint-disable-next-line no-unused-vars -const TVReviewsResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generateReviewEndpoint(parent.id, 'tv')); - - return response.data.results; - } catch (err) { - console.log('The /reviews endpoint failed'); - console.log(err); - return err.response; - } -}; - -module.exports = TVReviewsResolver; diff --git a/resolvers/Reviews/index.js b/resolvers/Reviews/index.js deleted file mode 100644 index 9542117..0000000 --- a/resolvers/Reviews/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const MovieReviewResolver = require('./MovieResolver'); -const ShowReviewResolver = require('./ShowResolver'); - -module.exports = { - MovieReviewResolver, - ShowReviewResolver -}; diff --git a/resolvers/SingleItemLookup/MovieResolver.js b/resolvers/SingleItemLookup/MovieResolver.js deleted file mode 100644 index 191199e..0000000 --- a/resolvers/SingleItemLookup/MovieResolver.js +++ /dev/null @@ -1,150 +0,0 @@ -const axios = require('axios'); -const { find, has } = require('lodash'); - -const generateSingleItemLookupEndpoint = require('../../utils/generateEndpoints/SingleItemLookup'); -const generateSearchEndpoint = require('../../utils/generateEndpoints/Search'); -const generateAbsolutePath = require('../../utils/images/generateAbsolutePath'); -const toPercentage = require('../../utils/maths/toPercentage'); -const generateYear = require('../../utils/dates/generateYear'); - -// eslint-disable-next-line no-unused-vars -const SearchForAMovieResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generateSearchEndpoint(args.search, 'movie')); - - const SingleMovie = find(response.data.results, (movie) => movie.id === args.id); - - try { - const SingleMovieResponse = await axios.get( - generateSingleItemLookupEndpoint(SingleMovie.id, 'movie') - ); - - const { data } = SingleMovieResponse; - - const releaseDate = generateYear(data.release_date); - const voteAverage = toPercentage(data.vote_average); - - const Movie = { - id: data.id ? data.id : 0, - name: data.title || '', - overview: data.overview || '', - backgroundUrl: generateAbsolutePath(data.backdrop_path) || '', - posterUrl: generateAbsolutePath(data.poster_path) || '', - genres: data.genres.length !== 0 ? data.genres : [], - homepage: data.homepage || '', - originalLanguage: data.original_language, - productionCompanies: [], - releaseDate: releaseDate !== '-' ? releaseDate : '', - voteAverage: String(voteAverage) ? voteAverage : '', - status: data.status || '', - tagline: data.tagline || '', - belongsToCollection: { - id: 0, - name: '', - backgroundUrl: '', - posterUrl: '' - }, - runtime: data.runtime || '', - - // Movie Specific fields - budget: data.budget ? `$${data.budget.toLocaleString()}` : '-', - revenue: data.revenue ? `$${data.revenue.toLocaleString()}` : '-' - }; - - // Name - if (Movie.name === '') { - Movie.name = data.original_title; - } - - // Original language - if ( - Movie.originalLanguage !== '' && - has(data, 'spoken_languages') === true && - data.spoken_languages.length !== 0 - ) { - // Finds the first language which matches the original_Language assigned to the movie - const language = data.spoken_languages.find( - /** - * @param {Object} el - */ - (el) => el.iso_639_1 === data.original_language - ); - - Movie.originalLanguage = language.name; - } - - // Production companies - // When the production company is available and there isn't 0 companies - if (has(data, 'production_companies') === true) { - const productionCompanies = []; - - data.production_companies.forEach((singleCompany) => { - const company = { - id: String(singleCompany.id) ? singleCompany.id : 0, - logo: generateAbsolutePath(singleCompany.logo_path) || '', - name: singleCompany.name || '' - }; - - // Push the new production company object to the productionCompanies array - productionCompanies.push(company); - }); - - Movie.productionCompanies = productionCompanies; - } - - // Show Collections - // When the belongs_to_collection is available and the collection is not null update the default values - if (has(data, 'belongs_to_collection') === true && data.belongs_to_collection !== null) { - // eslint-disable-next-line camelcase - const { belongs_to_collection } = data; - - /* - - Setting the collection values: - - - Numbers are converted to string to check to see if they are truthy or false value - - - When a number has been converted make sure to return it as a number - - */ - - // Sets the collection id - Movie.belongsToCollection.id = String(belongs_to_collection.id) - ? belongs_to_collection.id - : ''; - - // Sets the collection name - Movie.belongsToCollection.name = belongs_to_collection.name || ''; - - // Sets the backgroundUrl (Used as the background for the collection card) - Movie.belongsToCollection.backgroundUrl = - generateAbsolutePath(belongs_to_collection.backdrop_path) || ''; - - // Sets the posterUrl (Not used in the collection card, but it could be eventually) - Movie.belongsToCollection.posterUrl = - generateAbsolutePath(belongs_to_collection.poster_path) || ''; - } - - // Runtime - // When the runtime is available overwrite with a movie/show length e.g. 1hr 2min - if (Movie.runtime !== '') { - const hours = Math.floor(data.runtime / 60); - const minutes = data.runtime % 60; - Movie.runtime = `${hours}h ${minutes}m`; - } - - return Movie; - } catch (err) { - console.log(`The /Movie endpoint failed`); - console.log(err); - - return err.response; - } - } catch (err) { - console.log('The /Search endpoint failed'); - console.log(err.message); - return err.response; - } -}; - -module.exports = SearchForAMovieResolver; diff --git a/resolvers/SingleItemLookup/PersonResolver.js b/resolvers/SingleItemLookup/PersonResolver.js deleted file mode 100644 index 05ef1d8..0000000 --- a/resolvers/SingleItemLookup/PersonResolver.js +++ /dev/null @@ -1,48 +0,0 @@ -const axios = require('axios'); -const { find } = require('lodash'); - -const generateAbsolutePath = require('../../utils/images/generateAbsolutePath'); -const generateSearchEndpoint = require('../../utils/generateEndpoints/Search'); -const generateSingleItemLookupEndpoint = require('../../utils/generateEndpoints/SingleItemLookup'); -const generateBirthdayDate = require('../../utils/dates/generateBirthday'); - -// eslint-disable-next-line no-unused-vars -const SearchForAPersonResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generateSearchEndpoint(args.search, 'person')); - - const SinglePerson = find(response.data.results, (person) => person.id === args.id); - - try { - const SinglePersonResponse = await axios.get( - generateSingleItemLookupEndpoint(SinglePerson.id, 'person') - ); - - const { data } = SinglePersonResponse; - - const Person = { - id: String(data.id) ? data.id : 0, - birthday: generateBirthdayDate(data.birthday) || '', - knownForDepartment: data.known_for_department || '', - name: data.name || '', - alsoKnownAs: data.also_known_as, - gender: data.gender === 2 ? 'Male' : 'Female', - overview: data.overview || '', - placeOfBirth: data.place_of_birth, - posterUrl: generateAbsolutePath(data.profile_path) || '', - homepage: data.homepage || '' - }; - - return Person; - } catch (err) { - console.log(`The /Person endpoint failed`); - return err.response; - } - } catch (err) { - console.log('The /Search endpoint failed'); - console.log(err.response); - return err.response; - } -}; - -module.exports = SearchForAPersonResolver; diff --git a/resolvers/SingleItemLookup/ShowResolver.js b/resolvers/SingleItemLookup/ShowResolver.js deleted file mode 100644 index aebff65..0000000 --- a/resolvers/SingleItemLookup/ShowResolver.js +++ /dev/null @@ -1,205 +0,0 @@ -const axios = require('axios'); -const { find, has } = require('lodash'); - -const generateSingleItemLookupEndpoint = require('../../utils/generateEndpoints/SingleItemLookup'); -const generateAbsolutePath = require('../../utils/images/generateAbsolutePath'); -const generateSearchEndpoint = require('../../utils/generateEndpoints/Search'); -const generateYear = require('../../utils/dates/generateYear'); -const toPercentage = require('../../utils/maths/toPercentage'); - -// eslint-disable-next-line no-unused-vars -const SearchForAShowResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generateSearchEndpoint(args.search, 'tv')); - - const SingleItemLookup = find(response.data.results, (show) => show.id === args.id); - try { - const SingleItemLookupResponse = await axios.get( - generateSingleItemLookupEndpoint(SingleItemLookup.id, 'tv') - ); - - const { data } = SingleItemLookupResponse; - - const releaseDate = generateYear(data.first_air_date); - const voteAverage = toPercentage(data.vote_average); - - const Show = { - id: String(data.id) ? data.id : 0, - name: data.name || '', - overview: data.overview || '', - backgroundUrl: generateAbsolutePath(data.backdrop_path) || '', - posterUrl: generateAbsolutePath(data.poster_path) || '', - genres: data.genres.length !== 0 ? data.genres : [], - homepage: data.homepage || '', - originalLanguage: data.original_language, - productionCompanies: [], - releaseDate: releaseDate !== '-' ? releaseDate : '', - voteAverage: String(voteAverage) ? voteAverage : '', - status: data.status || '', - tagline: data.tagline || '', - belongsToCollection: { - id: 0, - name: '', - backgroundUrl: '', - posterUrl: '' - }, - runtime: data.episode_run_time || '', - - // Show Specific fields - type: data.type || '', - numberOfEpisodes: String(data.number_of_episodes) || 0, - numberOfSeasons: String(data.number_of_seasons) || 0, - CurrentSeason: { - backgroundUrl: '', - seasonNumber: 0, - year: '', - episodeCount: 0, - overview: '' - } - }; - - // Name - // When the name isn't available try to use the - if (Show.name === '') { - Show.name = data.original_name; - } - - // Original language - // If the below conditions are met overwrite the default originalLanguage to something meaningful e.g. "en" -> "English" - if ( - has(data, 'original_language') === true && - has(data, 'languages') === true && - data.languages.length !== 0 - ) { - // Finds the first language which matches the original_Language assigned to the Show - const language = data.languages.find((el) => el === data.original_language); - - Show.originalLanguage = language; - } - - // Production companies - // When the production company is available and there isn't 0 companies - if (has(data, 'production_companies') === true && data.production_companies.length !== 0) { - const productionCompanies = []; - - data.production_companies.forEach((singleCompany) => { - const company = { - id: String(singleCompany.id) ? singleCompany.id : 0, - logo: generateAbsolutePath(singleCompany.logo_path) || '', - name: singleCompany.name || '' - }; - - // Push the new production company object to the productionCompanies array - productionCompanies.push(company); - }); - - Show.productionCompanies = productionCompanies; - } - - // Show Collections - // When the belongs_to_collection is available update the default values - if (has(data, 'belongs_to_collection') === true) { - // eslint-disable-next-line camelcase - const { belongs_to_collection } = data; - - /* - - Setting the collection values: - - - Numbers are converted to string to check to see if they are truthy or false value - - - When a number has been converted make sure to return it as a number - - */ - - // Sets the collection id - Show.belongsToCollection.id = String(belongs_to_collection.id) - ? belongs_to_collection.id - : ''; - - // Sets the collection name - Show.belongsToCollection.name = belongs_to_collection.name || ''; - - // Sets the backgroundUrl (Used as the background for the collection card) - Show.belongsToCollection.backgroundUrl = - generateAbsolutePath(belongs_to_collection.backdrop_path) || ''; - - // Sets the posterUrl (Not used in the collection card, but it could be eventually) - Show.belongsToCollection.posterUrl = - generateAbsolutePath(belongs_to_collection.poster_path) || ''; - } - - // Runtime - // When the runtime is available overwrite with a movie/show length e.g. 1hr 2min - if (Show.runtime !== '') { - // Select the longest runtime - const runtime = Math.max(...data.episode_run_time); - - const hours = Math.floor(runtime / 60); - const minutes = runtime % 60; - Show.runtime = `${hours}h ${minutes}m`; - } - - /* - - Show specific fields - - */ - - if (has(data, 'last_episode_to_air') === true && has(data, 'seasons') === true) { - // eslint-disable-next-line camelcase - const { last_episode_to_air, seasons } = data; - - Show.CurrentSeason.backgroundUrl = - generateAbsolutePath(last_episode_to_air.still_path) || ''; - - Show.CurrentSeason.seasonNumber = String(last_episode_to_air.season_number) - ? last_episode_to_air.season_number - : 0; - - Show.CurrentSeason.year = generateYear(last_episode_to_air.air_date) || ''; - - /* - - CurrentSeason functionality - - To get the episodeCount and overview we need the CurrentSeasonIndex, this requires additional checks to be put in place. - - Checks put in place: - - - To ensure the index is found we check to see if the index is available ie not -1 which findIndex returns. - - - Once we have the index we then need to check if the properties actual exist in the object, if they are available set them otherwise the default values will be used (Show object) - - */ - const CurrentSeasonIndex = seasons.findIndex( - (season) => season.season_number === data.last_episode_to_air.season_number - ); - - if (CurrentSeasonIndex !== -1) { - // Episode count - if (has(seasons[CurrentSeasonIndex], 'episode_count') === true) { - Show.CurrentSeason.episodeCount = seasons[CurrentSeasonIndex].episode_count; - } - - // Overview - if (has(seasons[CurrentSeasonIndex], 'overview') === true) { - Show.CurrentSeason.overview = seasons[CurrentSeasonIndex].overview; - } - } - } - - return Show; - } catch (err) { - console.log(`The /tv endpoint failed`); - console.log(err); - return err.response; - } - } catch (err) { - console.log('The /Search endpoint failed'); - console.log(err); - return err.response; - } -}; - -module.exports = SearchForAShowResolver; diff --git a/resolvers/SingleItemLookup/index.js b/resolvers/SingleItemLookup/index.js deleted file mode 100644 index 8a1fd12..0000000 --- a/resolvers/SingleItemLookup/index.js +++ /dev/null @@ -1,9 +0,0 @@ -const SearchForAMovieResolver = require('./MovieResolver'); -const SearchForAShowResolver = require('./ShowResolver'); -const SearchForAPersonResolver = require('./PersonResolver'); - -module.exports = { - SearchForAMovieResolver, - SearchForAShowResolver, - SearchForAPersonResolver -}; diff --git a/resolvers/Social/MovieResolver.js b/resolvers/Social/MovieResolver.js deleted file mode 100644 index a8fbfc6..0000000 --- a/resolvers/Social/MovieResolver.js +++ /dev/null @@ -1,22 +0,0 @@ -const axios = require('axios'); - -const generateSocialLinksEndpoint = require('../../utils/generateEndpoints/Social'); -const setSocialLinks = require('../../utils/resolverUtils/Social/setSocialLinks'); - -// eslint-disable-next-line no-unused-vars -const MovieSocialResolver = async (parent, args, info, context) => { - try { - const response = await axios.get(generateSocialLinksEndpoint(parent.id, 'movie')); - - const { data } = response; - - const SocialLinks = setSocialLinks({ ...data, homepage: parent.homepage }); - - return SocialLinks; - } catch (err) { - console.log('The movie /external_ids (social) endpoint failed'); - return err.response; - } -}; - -module.exports = MovieSocialResolver; diff --git a/resolvers/Social/PersonResolver.js b/resolvers/Social/PersonResolver.js deleted file mode 100644 index f87ce96..0000000 --- a/resolvers/Social/PersonResolver.js +++ /dev/null @@ -1,22 +0,0 @@ -const axios = require('axios'); - -const generateSocialLinksEndpoint = require('../../utils/generateEndpoints/Social'); -const setSocialLinks = require('../../utils/resolverUtils/Social/setSocialLinks'); - -// eslint-disable-next-line no-unused-vars -const PersonSocialResolver = async (parent, args, info, context) => { - try { - const response = await axios.get(generateSocialLinksEndpoint(parent.id, 'person', 'person')); - - const { data } = response; - - const SocialLinks = setSocialLinks({ ...data, homepage: parent.homepage }, 'person'); - - return SocialLinks; - } catch (err) { - console.log('The Person /external_ids (social) endpoint failed'); - return err.response; - } -}; - -module.exports = PersonSocialResolver; diff --git a/resolvers/Social/ShowResolver.js b/resolvers/Social/ShowResolver.js deleted file mode 100644 index 35fa762..0000000 --- a/resolvers/Social/ShowResolver.js +++ /dev/null @@ -1,22 +0,0 @@ -const axios = require('axios'); - -const generateSocialLinksEndpoint = require('../../utils/generateEndpoints/Social'); -const setSocialLinks = require('../../utils/resolverUtils/Social/setSocialLinks'); - -// eslint-disable-next-line no-unused-vars -const TVSocialResolver = async (parent, args, info, context) => { - try { - const response = await axios.get(generateSocialLinksEndpoint(parent.id, 'tv')); - - const { data } = response; - - const SocialLinks = setSocialLinks({ ...data, homepage: parent.homepage }); - - return SocialLinks; - } catch (err) { - console.log('The TV /external_ids (social) endpoint failed'); - return err.response; - } -}; - -module.exports = TVSocialResolver; diff --git a/resolvers/Social/index.js b/resolvers/Social/index.js deleted file mode 100644 index 9a78e66..0000000 --- a/resolvers/Social/index.js +++ /dev/null @@ -1,9 +0,0 @@ -const MovieSocialResolver = require('./MovieResolver'); -const ShowSocialResolver = require('./ShowResolver'); -const PersonSocialResolver = require('./PersonResolver'); - -module.exports = { - MovieSocialResolver, - ShowSocialResolver, - PersonSocialResolver -}; diff --git a/resolvers/TopRated/MoviesResolver.js b/resolvers/TopRated/MoviesResolver.js deleted file mode 100644 index 77bf295..0000000 --- a/resolvers/TopRated/MoviesResolver.js +++ /dev/null @@ -1,24 +0,0 @@ -const axios = require('axios'); - -const generateTopRatedEndpoint = require('../../utils/generateEndpoints/TopRated'); -const setMovies = require('../../utils/resolverUtils/Movies/setMovies'); - -// eslint-disable-next-line no-unused-vars -const MoviePopularResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generateTopRatedEndpoint('movie')); - - const { data } = response; - const { results } = data; - - const Movies = setMovies(results); - - return Movies; - } catch (err) { - console.log('The /movie/top_rated endpoint failed'); - console.log(err); - return err.response; - } -}; - -module.exports = MoviePopularResolver; diff --git a/resolvers/TopRated/ShowResolver.js b/resolvers/TopRated/ShowResolver.js deleted file mode 100644 index 460eead..0000000 --- a/resolvers/TopRated/ShowResolver.js +++ /dev/null @@ -1,23 +0,0 @@ -const axios = require('axios'); - -const generateTopRatedEndpoint = require('../../utils/generateEndpoints/TopRated'); -const setShows = require('../../utils/resolverUtils/Shows/setShows'); - -// eslint-disable-next-line no-unused-vars -const PopularShowResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generateTopRatedEndpoint('tv')); - - const { data } = response; - const { results } = data; - - const Shows = setShows(results); - - return Shows; - } catch (err) { - console.log('The /movie/top_rated endpoint failed'); - return err.response; - } -}; - -module.exports = PopularShowResolver; diff --git a/resolvers/TopRated/index.js b/resolvers/TopRated/index.js deleted file mode 100644 index 4e8b835..0000000 --- a/resolvers/TopRated/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const TopRatedMoviesResolver = require('./MoviesResolver'); -const TopRatedShowsResolver = require('./ShowResolver'); - -module.exports = { - TopRatedMoviesResolver, - TopRatedShowsResolver -}; diff --git a/resolvers/Upcoming/MoviesResolver.js b/resolvers/Upcoming/MoviesResolver.js deleted file mode 100644 index de0e155..0000000 --- a/resolvers/Upcoming/MoviesResolver.js +++ /dev/null @@ -1,23 +0,0 @@ -const axios = require('axios'); - -const generateUpcomingEndpoint = require('../../utils/generateEndpoints/Upcoming'); -const setMovies = require('../../utils/resolverUtils/Movies/setMovies'); - -// eslint-disable-next-line no-unused-vars -const NowPlayingTVResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generateUpcomingEndpoint('movie')); - - const { data } = response; - const { results } = data; - - const Movies = setMovies(results); - - return Movies; - } catch (err) { - console.log('The tv/on_the_air endpoint failed'); - return err.response; - } -}; - -module.exports = NowPlayingTVResolver; diff --git a/resolvers/Upcoming/ShowResolver.js b/resolvers/Upcoming/ShowResolver.js deleted file mode 100644 index 2e8eae8..0000000 --- a/resolvers/Upcoming/ShowResolver.js +++ /dev/null @@ -1,23 +0,0 @@ -const axios = require('axios'); - -const generateUpcomingEndpoint = require('../../utils/generateEndpoints/Upcoming'); -const setShows = require('../../utils/resolverUtils/Shows/setShows'); - -// eslint-disable-next-line no-unused-vars -const NowPlayingTVResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generateUpcomingEndpoint('tv')); - - const { data } = response; - const { results } = data; - - const Shows = setShows(results); - - return Shows; - } catch (err) { - console.log('The tv/on_the_air endpoint failed'); - return err.response; - } -}; - -module.exports = NowPlayingTVResolver; diff --git a/resolvers/Upcoming/index.js b/resolvers/Upcoming/index.js deleted file mode 100644 index 9bd3990..0000000 --- a/resolvers/Upcoming/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const UpcomingShowsResolver = require('./ShowResolver'); -const UpcomingMoviesResolver = require('./MoviesResolver'); - -module.exports = { - UpcomingShowsResolver, - UpcomingMoviesResolver -}; diff --git a/resolvers/Videos/MovieResolver.js b/resolvers/Videos/MovieResolver.js deleted file mode 100644 index c0d4fe7..0000000 --- a/resolvers/Videos/MovieResolver.js +++ /dev/null @@ -1,23 +0,0 @@ -const axios = require('axios'); - -const generateVideoEndpoint = require('../../utils/generateEndpoints/Videos'); -const setFeaturedVideo = require('../../utils/resolverUtils/Videos/setFeaturedVideo'); - -// eslint-disable-next-line no-unused-vars -const MovieVideoResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generateVideoEndpoint(parent.id, 'movie')); - - const { data } = response; - const { results } = data; - - const Video = setFeaturedVideo(results); - - return Video; - } catch (err) { - console.log('The movie/videos endpoint failed'); - return err.response; - } -}; - -module.exports = MovieVideoResolver; diff --git a/resolvers/Videos/ShowResolver.js b/resolvers/Videos/ShowResolver.js deleted file mode 100644 index b5f1acc..0000000 --- a/resolvers/Videos/ShowResolver.js +++ /dev/null @@ -1,23 +0,0 @@ -const axios = require('axios'); - -const generateVideoEndpoint = require('../../utils/generateEndpoints/Videos'); -const setFeaturedVideo = require('../../utils/resolverUtils/Videos/setFeaturedVideo'); - -// eslint-disable-next-line no-unused-vars -const ShowVideoResolver = async (parent, args, context, info) => { - try { - const response = await axios.get(generateVideoEndpoint(parent.id, 'tv')); - - const { data } = response; - const { results } = data; - - const Video = setFeaturedVideo(results); - - return Video; - } catch (err) { - console.log('The tv/videos endpoint failed'); - return err.response; - } -}; - -module.exports = ShowVideoResolver; diff --git a/resolvers/Videos/index.js b/resolvers/Videos/index.js deleted file mode 100644 index 6632166..0000000 --- a/resolvers/Videos/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const MovieVideoResolver = require('./MovieResolver'); -const ShowVideoResolver = require('./ShowResolver'); - -module.exports = { - MovieVideoResolver, - ShowVideoResolver -}; diff --git a/resolvers/index.js b/resolvers/index.js deleted file mode 100644 index 160d129..0000000 --- a/resolvers/index.js +++ /dev/null @@ -1,108 +0,0 @@ -// Reviews resolvers -const { MovieReviewResolver, ShowReviewResolver } = require('./Reviews'); - -// Cast resolvers -const { MovieCastResolver, ShowCastResolver } = require('./Cast'); - -// Crew resolvers -const { MovieCrewResolver, ShowCrewResolver } = require('./Crew'); - -// Recommendation resolvers -const { MovieRecommendationsResolver, ShowRecommendationsResolver } = require('./Recommendations'); - -// Keyword resolver -const { MovieKeywordResolver, ShowKeywordResolver } = require('./Keywords'); - -// Social resolver -const { MovieSocialResolver, ShowSocialResolver, PersonSocialResolver } = require('./Social'); - -// Search for a movie/show resolvers -const { - SearchForAMovieResolver, - SearchForAShowResolver, - SearchForAPersonResolver -} = require('./SingleItemLookup'); - -// Discover resolvers -const { DiscoverMoviesResolver, DiscoverShowsResolver } = require('./Discover'); - -// Popular resolvers -const { PopularShowsResolver, PopularMoviesResolver, PopularPeopleResolver } = require('./Popular'); - -// NowPlaying resolvers -const { NowPlayingShowsResolver, NowPlayingMovieResolver } = require('./NowPlaying'); - -// Upcoming resolvers -const { UpcomingShowsResolver, UpcomingMoviesResolver } = require('./Upcoming'); - -// TopRated resolvers -const { TopRatedMoviesResolver, TopRatedShowsResolver } = require('./TopRated'); - -// Video resolvers -const { MovieVideoResolver, ShowVideoResolver } = require('./Videos'); - -// Credits resolver -const { CreditsResolver } = require('./Credits'); - -const resolvers = { - // Additional data for the single movie object - Movie: { - reviews: MovieReviewResolver, - featuredCast: MovieCastResolver, - featuredCrew: MovieCrewResolver, - recommendations: MovieRecommendationsResolver, - keywords: MovieKeywordResolver, - social: MovieSocialResolver, - featuredVideo: MovieVideoResolver - }, - - // Additional data for the single show object - Show: { - reviews: ShowReviewResolver, - featuredCast: ShowCastResolver, - featuredCrew: ShowCrewResolver, - recommendations: ShowRecommendationsResolver, - keywords: ShowKeywordResolver, - social: ShowSocialResolver, - featuredVideo: ShowVideoResolver - }, - - // Additional data for the single person object - Person: { - credits: CreditsResolver, - social: PersonSocialResolver - }, - - // Root query - Query: { - // SingleItemLookup - SearchForAMovie: SearchForAMovieResolver, - SearchForAShow: SearchForAShowResolver, - SearchForAPerson: SearchForAPersonResolver, - - // Discover - DiscoverMovies: DiscoverMoviesResolver, - DiscoverShows: DiscoverShowsResolver, - - // Popular - PopularMovies: PopularMoviesResolver, - PopularShows: PopularShowsResolver, - PopularPeople: PopularPeopleResolver, - - // NowPlaying - NowPlayingShows: NowPlayingShowsResolver, - NowPlayingMovies: NowPlayingMovieResolver, - - // Upcoming resolvers - UpcomingShows: UpcomingShowsResolver, - UpcomingMovies: UpcomingMoviesResolver, - - // Top rated resolvers - TopRatedMovies: TopRatedMoviesResolver, - TopRatedShows: TopRatedShowsResolver - } -}; - -module.exports = { - RootQuery: resolvers -}; diff --git a/schema.js b/schema.js deleted file mode 100644 index 2f943c6..0000000 --- a/schema.js +++ /dev/null @@ -1,62 +0,0 @@ -const { makeExecutableSchema } = require('@graphql-tools/schema'); - -// Generic models for specific sets of data e.g. Cast, Genre etc -const Cast = require('./models/Cast'); -const Company = require('./models/Company'); -const Crew = require('./models/Crew'); -const Genre = require('./models/Genres'); -const Keyword = require('./models/Keyword'); -const Social = require('./models/Social'); -const Review = require('./models/Review'); -const BelowsToCollection = require('./models/BelongsToCollection'); - -// Person Models -const People = require('./models/People'); -const Person = require('./models/People/Person'); -const Credits = require('./models/People/Credits'); - -// Show Models -const Network = require('./models/Show/Network'); -const CurrentSeason = require('./models/Show/CurrentSeason'); - -// Main models e.g. SingleMovie, TV, People etc -const Query = require('./models/Query'); -const Show = require('./models/Show'); -const Shows = require('./models/Show/Shows'); -const Video = require('./models/Videos'); - -const Movies = require('./models/Movie/Movies'); -const Movie = require('./models/Movie'); - -// Resolvers -const { RootQuery } = require('./resolvers'); - -const stuff = makeExecutableSchema({ - typeDefs: [ - Query, - Cast, - Company, - Crew, - Genre, - Keyword, - Social, - Review, - Network, - Show, - Shows, - Movie, - Video, - Person, - Movie, - Movies, - People, - CurrentSeason, - Credits, - BelowsToCollection - ], - resolvers: RootQuery -}); - -module.exports = { - schema: stuff -}; diff --git a/src/app.module.ts b/src/app.module.ts new file mode 100644 index 0000000..8392e50 --- /dev/null +++ b/src/app.module.ts @@ -0,0 +1,25 @@ +import { Module } from '@nestjs/common'; + +import { graphqlConfig } from './config/graphql.config'; +import { serviceConfig } from './config/service.config'; +import { DiscoverFilteringModule } from './modules/discover/filtering/discover-filtering.module'; +import { DiscoverFormDataModule } from './modules/discover/form-data/discover-form-data.module'; +import { FilteringOptionsModule } from './modules/discover/options/filtering-options.module'; +import { MovieModule } from './modules/movies/movie.module'; +import { PersonModule } from './modules/person/person.module'; +import { ShowModule } from './modules/shows/show.module'; + +@Module({ + imports: [ + serviceConfig, + graphqlConfig, + + ShowModule, + MovieModule, + PersonModule, + DiscoverFormDataModule, + FilteringOptionsModule, + DiscoverFilteringModule + ] +}) +export class AppModule {} diff --git a/src/common/README.md b/src/common/README.md new file mode 100644 index 0000000..d56cde1 --- /dev/null +++ b/src/common/README.md @@ -0,0 +1,36 @@ +# Common Directory + +The Common directory is dedicated to shared code that is specific to the application's business logic and domain models, but needs to be reused across multiple features. Unlike core utilities, items in Common are tied to the application's domain concepts. + +## Purpose + +- Houses shared business logic and domain-specific utilities +- Provides reusable components that implement business rules +- Contains shared types and interfaces related to domain models +- Centralizes common validation logic and business constraints + +## Examples of What Goes Here + +1. Shared business validation rules +2. Common domain model transformations +3. Business-specific utility functions +4. Shared domain interfaces and types +5. Common business calculations or formulas + +## When to Use Common + +Use the Common directory when: + +- The code implements business rules needed by multiple features +- You have domain-specific logic that's reused across different modules +- You need to share business validation or transformation logic +- Multiple features need access to the same domain-specific utilities + +## When Not to Use Common + +Don't use Common for: + +- Generic utility functions (use Core instead) +- Framework-specific code +- Infrastructure concerns +- Pure technical utilities with no business logic diff --git a/src/common/lib/index.ts b/src/common/lib/index.ts new file mode 100644 index 0000000..0a38928 --- /dev/null +++ b/src/common/lib/index.ts @@ -0,0 +1 @@ +export * from './isNonNullable'; diff --git a/src/common/lib/isNonNullable.ts b/src/common/lib/isNonNullable.ts new file mode 100644 index 0000000..4d5870f --- /dev/null +++ b/src/common/lib/isNonNullable.ts @@ -0,0 +1 @@ +export const isNonNullable = (value: T): value is NonNullable => value != null; diff --git a/src/common/models/Pagination.ts b/src/common/models/Pagination.ts new file mode 100644 index 0000000..88cfb70 --- /dev/null +++ b/src/common/models/Pagination.ts @@ -0,0 +1,17 @@ +export class Pagination { + readonly results: Array; + readonly meta: { + page: number; + pageCount: number; + total: number; + }; + + constructor(results: Array, pageNumber: number, pageCount: number, total: number) { + this.results = results; + this.meta = { + page: pageNumber, + pageCount, + total + }; + } +} diff --git a/src/common/types/Nullable.ts b/src/common/types/Nullable.ts new file mode 100644 index 0000000..aa32b2d --- /dev/null +++ b/src/common/types/Nullable.ts @@ -0,0 +1 @@ +export type Nullable = T | null; diff --git a/src/config/README.md b/src/config/README.md new file mode 100644 index 0000000..6fbabcc --- /dev/null +++ b/src/config/README.md @@ -0,0 +1,48 @@ +# Config Directory + +The Config directory contains application-wide configuration settings, environment variables, and setup code that defines how the application behaves in different environments. + +## Purpose + +- Centralizes all configuration-related code and settings +- Manages environment-specific variables and settings +- Defines application-wide setup and initialization +- Houses configuration interfaces and types +- Provides configuration factories and providers + +## What Goes Here + +1. Environment configuration files +2. Service configuration providers +3. Module configuration factories +4. GraphQL and API configurations +5. Type generation configurations +6. Path and file location configurations +7. Global application settings + +## When to Use Config + +Use the Config directory when: + +- Adding new application-wide settings +- Defining environment-specific configurations +- Setting up module or service configurations +- Managing external service connections +- Defining global type generation settings + +## When Not to Use Config + +Don't use Config for: + +- Feature-specific settings (belong in respective modules) +- Business logic or rules +- Utility functions +- Runtime data management +- Local component configurations + +## Current Configuration Files + +- `graphql.config.ts` - GraphQL module configuration +- `service.config.ts` - Core service settings +- `filePaths.ts` - GraphQL schema file path management +- `schemaToTypings.ts` - TypeScript definitions generation config diff --git a/src/config/filePaths.ts b/src/config/filePaths.ts new file mode 100644 index 0000000..649e5ad --- /dev/null +++ b/src/config/filePaths.ts @@ -0,0 +1,77 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { join } from 'path'; + +const GRAPHQL_BASE_PATH = 'graphql'; +/** + * Gets the GraphQL schema file paths in either absolute or relative format + * + * Two different path formats are needed for different use cases: + * + * 1. Absolute paths (starting with 'src/'): + * - Required by NestJS GraphQL module to properly load schema files + * - Used in app.module.ts via graphql.config.ts for runtime schema loading + * - The NestJS GraphQL module specifically requires paths to start with 'src/' + * to properly resolve and watch schema files during development + * - Example: 'src/graphql/models/Query.graphql' + * + * 2. Relative paths (from project root): + * - Required by GraphQLDefinitionsFactory for TypeScript interface generation + * - Used in schema-to-typings.ts which runs as a separate process outside NestJS + * - Since this runs as a separate script, it needs full filesystem paths relative + * to the project root to locate the schema files + * - Example: '/path/to/project/graphql/models/Query.graphql' + * + * This dual path handling ensures both: + * - Runtime schema loading works correctly in the NestJS application + * - Type generation script can find and process schema files from the command line + * + * @param pathType - Whether to return absolute or relative paths + * @returns Array of GraphQL schema file paths in the requested format + */ +export function getGraphQLPaths(pathType: 'absolute' | 'relative' = 'absolute') { + const graphqlPaths = [ + // Enums used by all the graphql schemas + 'models/Common/CommonEnums.graphql', + 'models/Common/CommonPagination.graphql', + + // Entertainment specific models + 'models/Entertainment/BelongsToCollection.graphql', + 'models/Entertainment/Cast.graphql', + 'models/Entertainment/Company.graphql', + 'models/Entertainment/Crew.graphql', + 'models/Entertainment/Genre.graphql', + 'models/Entertainment/Keyword.graphql', + 'models/Entertainment/Recommendation.graphql', + 'models/Entertainment/Review.graphql', + 'models/Entertainment/Social.graphql', + 'models/Entertainment/Video.graphql', + + // Discover specific models + 'models/Discover/RangeFilters.graphql', + 'models/Discover/FiltersInput.graphql', + 'models/Discover.graphql', + 'models/Discover/DiscoverResult.graphql', + 'models/Discover/FiltersFormData.graphql', + + // Person specific models + 'models/Person/Credit.graphql', + 'models/Person/CreditGroup.graphql', + 'models/Person/PersonCredits.graphql', + 'models/Person/Person.graphql', + + // Individual resource schemas + 'models/Show/Show.graphql', + 'models/Movie/Movie.graphql', + 'models/Person/Person.graphql', + + 'models/Query.graphql' + ]; + + if (pathType === 'absolute') { + // For NestJS GraphQL module (absolute paths starting with src/) + return graphqlPaths.map((path) => `src/${GRAPHQL_BASE_PATH}/${path}`); + } + + // For GraphQLDefinitionsFactory (relative paths from project root) + return graphqlPaths.map((path) => join(process.cwd(), `../${GRAPHQL_BASE_PATH}/${path}`)); +} diff --git a/src/config/graphql.config.ts b/src/config/graphql.config.ts new file mode 100644 index 0000000..91d4f52 --- /dev/null +++ b/src/config/graphql.config.ts @@ -0,0 +1,12 @@ +import { ApolloDriverConfig, ApolloDriver } from '@nestjs/apollo'; +import { GraphQLModule } from '@nestjs/graphql'; + +import { getGraphQLPaths } from './filePaths'; + +export const graphqlConfig = GraphQLModule.forRoot({ + driver: ApolloDriver, + playground: true, + + // Must start with src/ for some reason otherwise nestjs doesn't pick up the files grr + typePaths: getGraphQLPaths('absolute') +}); diff --git a/src/config/graphql/generated/schema.ts b/src/config/graphql/generated/schema.ts new file mode 100644 index 0000000..c78828e --- /dev/null +++ b/src/config/graphql/generated/schema.ts @@ -0,0 +1,360 @@ +/* + * ------------------------------------------------------- + * THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) + * ------------------------------------------------------- + */ + +/* eslint-disable */ + +export enum ENTERTAINMENT_TYPES { + MOVIE = 'MOVIE', + TV = 'TV' +} + +export enum RESOURCE_TYPE { + TOP_RATED = 'TOP_RATED', + POPULAR = 'POPULAR', + NOW_PLAYING = 'NOW_PLAYING', + UPCOMING = 'UPCOMING', + AIRING_TODAY = 'AIRING_TODAY', + ON_THE_AIR = 'ON_THE_AIR' +} + +export interface DiscoverFormDataInput { + sort_by?: Nullable; + show_me?: Nullable; + with_watch_monetization_types?: Nullable[]>; + with_genres?: Nullable[]>; + certifications?: Nullable[]>; + with_release_types?: Nullable[]>; + release_date?: Nullable; + air_date?: Nullable; + with_original_language?: Nullable; + region?: Nullable; + vote_average?: Nullable; + with_runtime?: Nullable; + vote_count?: Nullable; + search_first_air_date?: Nullable; + restrict_services?: Nullable; + with_ott_providers?: Nullable[]>; +} + +export interface DateRangeFilterInput { + gte?: Nullable; + lte?: Nullable; +} + +export interface NumberRangeFilterInput { + gte?: Nullable; + lte?: Nullable; +} + +export interface VoteCountFilterInput { + gte?: Nullable; + lte?: Nullable; +} + +export interface PaginatedResult { + meta: PaginationMetaData; +} + +export interface DiscoverResult { + __typename?: 'DiscoverResult'; + adult?: Nullable; + backdropUrl?: Nullable; + posterUrl?: Nullable; + name?: Nullable; + homepage?: Nullable; + id: string; + originCountry?: Nullable[]>; + originalLanguage?: Nullable; + overview?: Nullable; + releaseDate?: Nullable; + popularity?: Nullable; + posterPath?: Nullable; + status?: Nullable; + tagline?: Nullable; + voteAverage?: Nullable; + voteCount?: Nullable; +} + +export interface PaginatedDiscoverResult extends PaginatedResult { + __typename?: 'PaginatedDiscoverResult'; + meta: PaginationMetaData; + results: DiscoverResult[]; +} + +export interface DiscoverFormData { + __typename?: 'DiscoverFormData'; + sort_by?: Nullable; + show_me?: Nullable; + with_watch_monetization_types?: Nullable[]>; + with_genres?: Nullable[]>; + certifications?: Nullable[]>; + with_release_types?: Nullable[]>; + release_date?: Nullable; + air_date?: Nullable; + with_original_language?: Nullable; + region?: Nullable; + vote_average?: Nullable; + with_runtime?: Nullable; + vote_count?: Nullable; + search_first_air_date?: Nullable; + restrict_services?: Nullable; + with_ott_providers?: Nullable[]>; +} + +export interface DateRangeFilter { + __typename?: 'DateRangeFilter'; + gte?: Nullable; + lte?: Nullable; +} + +export interface NumberRangeFilter { + __typename?: 'NumberRangeFilter'; + gte?: Nullable; + lte?: Nullable; +} + +export interface VoteCountFilter { + __typename?: 'VoteCountFilter'; + gte?: Nullable; + lte?: Nullable; +} + +export interface BelongsToCollection { + __typename?: 'BelongsToCollection'; + id?: Nullable; + name?: Nullable; + backgroundUrl?: Nullable; + posterUrl?: Nullable; +} + +export interface Cast { + __typename?: 'Cast'; + id?: Nullable; + character?: Nullable; + profileImageUrl?: Nullable; + gender?: Nullable; + episodeCount?: Nullable; +} + +export interface Company { + __typename?: 'Company'; + id?: Nullable; + logo?: Nullable; + name?: Nullable; +} + +export interface Crew { + __typename?: 'Crew'; + name?: Nullable; + roles?: Nullable; +} + +export interface Genre { + __typename?: 'Genre'; + id?: Nullable; + name?: Nullable; +} + +export interface Keyword { + __typename?: 'Keyword'; + id?: Nullable; + name?: Nullable; +} + +export interface Recommendation { + __typename?: 'Recommendation'; + name?: Nullable; + releaseDate?: Nullable; + backgroundUrl?: Nullable; + rating?: Nullable; +} + +export interface Author { + __typename?: 'Author'; + name?: Nullable; + username?: Nullable; + avatarUrl?: Nullable; + rating?: Nullable; +} + +export interface Review { + __typename?: 'Review'; + author?: Nullable; + isFeatured?: Nullable; + content?: Nullable; + createdOn?: Nullable; +} + +export interface Social { + __typename?: 'Social'; + id?: Nullable; + freebase_mid?: Nullable; + freebase_id?: Nullable; + imdb_id?: Nullable; + tvrage_id?: Nullable; + wikidata_id?: Nullable; + facebook_id?: Nullable; + instagram_id?: Nullable; + tiktok_id?: Nullable; + twitter_id?: Nullable; + youtube_id?: Nullable; +} + +export interface Video { + __typename?: 'Video'; + id?: Nullable; + name?: Nullable; + url?: Nullable; + type?: Nullable; + site?: Nullable; +} + +export interface Movie { + __typename?: 'Movie'; + id?: Nullable; + name?: Nullable; + overview?: Nullable; + backgroundUrl?: Nullable; + posterUrl?: Nullable; + genres?: Nullable[]>; + homepage?: Nullable; + originalLanguage?: Nullable; + productionCompanies?: Nullable[]>; + releaseDate?: Nullable; + voteAverage?: Nullable; + status?: Nullable; + review?: Nullable; + recommendations?: Nullable[]>; + keywords?: Nullable[]>; + social?: Nullable; + topBilledCast?: Nullable[]>; + featuredCrew?: Nullable[]>; + youtubeVideo?: Nullable