├── .nvmrc ├── apps ├── .gitkeep ├── next-app │ ├── public │ │ └── .gitkeep │ ├── index.d.ts │ ├── next-env.d.ts │ ├── pages │ │ ├── index.tsx │ │ ├── _app.tsx │ │ └── api │ │ │ └── trpc │ │ │ └── [trpc].ts │ ├── specs │ │ └── index.spec.tsx │ ├── jest.config.ts │ ├── next.config.js │ ├── tsconfig.spec.json │ ├── tsconfig.json │ ├── .eslintrc.json │ └── project.json ├── mobile │ ├── test-setup.ts │ ├── assets │ │ ├── icon.png │ │ ├── splash.png │ │ ├── favicon.png │ │ ├── adaptive-icon.png │ │ └── star.svg │ ├── src │ │ └── app │ │ │ ├── icons │ │ │ ├── logo.png │ │ │ ├── chevron-right.svg │ │ │ ├── terminal.svg │ │ │ ├── heart.svg │ │ │ ├── pointer.svg │ │ │ ├── blog.svg │ │ │ ├── book.svg │ │ │ ├── nx-cloud.svg │ │ │ ├── youtube.svg │ │ │ ├── vscode.svg │ │ │ ├── courses.svg │ │ │ ├── checkmark.svg │ │ │ └── github.svg │ │ │ ├── App.spec.tsx │ │ │ ├── ReactotronConfig.ts │ │ │ └── App.tsx │ ├── babel.config.js │ ├── tsconfig.app.json │ ├── jest.config.ts │ ├── index.js │ ├── .expo-shared │ │ ├── assets.json │ │ └── README.md │ ├── tsconfig.spec.json │ ├── .eslintrc.json │ ├── webpack.config.js │ ├── tsconfig.json │ ├── jest.config.js │ ├── eas.json │ ├── .gitignore │ ├── metro.config.js │ ├── app.config.js │ ├── package.json │ └── project.json ├── next-app-e2e │ ├── src │ │ ├── support │ │ │ ├── app.po.ts │ │ │ ├── index.ts │ │ │ └── commands.ts │ │ ├── fixtures │ │ │ └── example.json │ │ └── integration │ │ │ └── app.spec.ts │ ├── .eslintrc.json │ ├── tsconfig.json │ ├── cypress.json │ └── project.json └── mobile-e2e │ ├── test-setup.ts │ ├── .babelrc │ ├── tsconfig.json │ ├── tsconfig.e2e.json │ ├── src │ └── app.spec.ts │ ├── .eslintrc.json │ ├── jest.config.json │ ├── environment.js │ ├── project.json │ └── .detoxrc.json ├── libs ├── .gitkeep ├── database │ └── edgedb-client │ │ ├── .gitignore │ │ ├── package.json │ │ ├── src │ │ ├── lib │ │ │ ├── client.ts │ │ │ └── edgedb-client.ts │ │ └── index.ts │ │ ├── .babelrc │ │ ├── tsconfig.spec.json │ │ ├── tsconfig.lib.json │ │ ├── README.md │ │ ├── .eslintrc.json │ │ ├── jest.config.ts │ │ ├── tsconfig.json │ │ └── project.json ├── frontend │ ├── trpc-client │ │ ├── src │ │ │ ├── index.ts │ │ │ └── lib │ │ │ │ ├── trpc-client.spec.ts │ │ │ │ └── trpc-client.ts │ │ ├── .babelrc │ │ ├── package.json │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── README.md │ │ ├── .eslintrc.json │ │ ├── jest.config.ts │ │ ├── tsconfig.spec.json │ │ └── project.json │ └── mobile-app │ │ ├── .babelrc │ │ ├── test-setup.ts │ │ ├── custom.d.ts │ │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── features │ │ │ ├── movie │ │ │ │ └── detail-screen │ │ │ │ │ ├── stringifyMovieId.tsx │ │ │ │ │ ├── parseMovieId.tsx │ │ │ │ │ ├── DetailScreen.spec.tsx │ │ │ │ │ └── DetailScreen.tsx │ │ │ └── home │ │ │ │ ├── Home.spec.tsx │ │ │ │ ├── ButtonLink.tsx │ │ │ │ ├── Home.tsx │ │ │ │ └── MoviePoster.tsx │ │ │ ├── navigation │ │ │ ├── Navigation.spec.tsx │ │ │ ├── MovieList.tsx │ │ │ ├── Navigation.tsx │ │ │ ├── MyWatchlist.tsx │ │ │ ├── RootNavigator.tsx │ │ │ ├── tmdb_logo.svg │ │ │ ├── TabStack.tsx │ │ │ └── AuthStack.tsx │ │ │ ├── native-base │ │ │ ├── NativeBase.spec.tsx │ │ │ └── NativeBaseProvider.tsx │ │ │ └── providers │ │ │ ├── Provider.tsx │ │ │ ├── AuthenticationProvider.tsx │ │ │ ├── NavigationProvider.tsx │ │ │ └── TRPCProvider.tsx │ │ ├── package.json │ │ ├── README.md │ │ ├── tsconfig.lib.json │ │ ├── jest.config.ts │ │ ├── tsconfig.spec.json │ │ ├── .eslintrc.json │ │ ├── tsconfig.json │ │ ├── jest.config.js │ │ └── project.json └── api │ └── trpc-server-edgedb │ ├── package.json │ ├── .babelrc │ ├── src │ ├── index.ts │ └── lib │ │ ├── router.ts │ │ ├── MoviesSchema.ts │ │ ├── context.ts │ │ ├── trpc-helper.ts │ │ ├── user-router.ts │ │ └── tmdb-router.ts │ ├── tsconfig.spec.json │ ├── tsconfig.lib.json │ ├── README.md │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── tsconfig.json │ └── project.json ├── tools ├── generators │ └── .gitkeep └── tsconfig.tools.json ├── edgedb.toml ├── .env.example ├── babel.config.json ├── .prettierrc ├── ss-signin.png ├── ss-signout.png ├── ss-splash.png ├── edgedb-globals.png ├── ss-mywatchlist.png ├── ss-nowplaying.png ├── .prettierignore ├── jest.preset.js ├── jest.config.ts ├── .vscode └── extensions.json ├── .editorconfig ├── dbschema ├── migrations │ ├── 00003.edgeql │ ├── 00005.edgeql │ ├── 00002.edgeql │ ├── 00004.edgeql │ ├── 00007.edgeql │ ├── 00001.edgeql │ └── 00006.edgeql └── default.esdl ├── workspace.json ├── .github └── workflows │ └── develop.yml ├── .eslintrc.json ├── .gitignore ├── nx.json ├── tsconfig.base.json ├── README.md └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 -------------------------------------------------------------------------------- /apps/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/generators/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/next-app/public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /edgedb.toml: -------------------------------------------------------------------------------- 1 | [edgedb] 2 | server-version = "2.0" 3 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | TMDB_BEARER_TOKEN='your-tmdb-bearer-token' -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "babelrcRoots": ["*"] 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false 4 | } 5 | -------------------------------------------------------------------------------- /libs/database/edgedb-client/.gitignore: -------------------------------------------------------------------------------- 1 | codegen 2 | src/lib/codegen 3 | -------------------------------------------------------------------------------- /libs/frontend/trpc-client/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/trpc-client'; 2 | -------------------------------------------------------------------------------- /apps/mobile/test-setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-native/extend-expect'; 2 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["babel-preset-expo"] 3 | } 4 | -------------------------------------------------------------------------------- /apps/next-app-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1'); 2 | -------------------------------------------------------------------------------- /ss-signin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwarger/tmdb-watchlist-edgedb/HEAD/ss-signin.png -------------------------------------------------------------------------------- /ss-signout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwarger/tmdb-watchlist-edgedb/HEAD/ss-signout.png -------------------------------------------------------------------------------- /ss-splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwarger/tmdb-watchlist-edgedb/HEAD/ss-splash.png -------------------------------------------------------------------------------- /libs/frontend/mobile-app/test-setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-native/extend-expect'; 2 | -------------------------------------------------------------------------------- /edgedb-globals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwarger/tmdb-watchlist-edgedb/HEAD/edgedb-globals.png -------------------------------------------------------------------------------- /ss-mywatchlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwarger/tmdb-watchlist-edgedb/HEAD/ss-mywatchlist.png -------------------------------------------------------------------------------- /ss-nowplaying.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwarger/tmdb-watchlist-edgedb/HEAD/ss-nowplaying.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | -------------------------------------------------------------------------------- /libs/frontend/trpc-client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]] 3 | } 4 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nrwl/jest/preset').default; 2 | 3 | module.exports = { ...nxPreset }; 4 | -------------------------------------------------------------------------------- /apps/mobile/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwarger/tmdb-watchlist-edgedb/HEAD/apps/mobile/assets/icon.png -------------------------------------------------------------------------------- /apps/mobile/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwarger/tmdb-watchlist-edgedb/HEAD/apps/mobile/assets/splash.png -------------------------------------------------------------------------------- /libs/frontend/trpc-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@conference-demos/trpc-client", 3 | "version": "0.0.1" 4 | } 5 | -------------------------------------------------------------------------------- /apps/mobile/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwarger/tmdb-watchlist-edgedb/HEAD/apps/mobile/assets/favicon.png -------------------------------------------------------------------------------- /libs/frontend/mobile-app/custom.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const content: any 3 | export default content 4 | } 5 | -------------------------------------------------------------------------------- /apps/mobile/src/app/icons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwarger/tmdb-watchlist-edgedb/HEAD/apps/mobile/src/app/icons/logo.png -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import { getJestProjects } from '@nrwl/jest'; 2 | 3 | export default { 4 | projects: getJestProjects(), 5 | }; 6 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/navigation/RootNavigator' 2 | export * from './lib/providers/Provider' 3 | -------------------------------------------------------------------------------- /apps/mobile-e2e/test-setup.ts: -------------------------------------------------------------------------------- 1 | import { device } from 'detox'; 2 | 3 | beforeAll(async () => { 4 | await device.launchApp(); 5 | }); 6 | -------------------------------------------------------------------------------- /apps/mobile/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mwarger/tmdb-watchlist-edgedb/HEAD/apps/mobile/assets/adaptive-icon.png -------------------------------------------------------------------------------- /apps/next-app-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /libs/database/edgedb-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@conference-demos/edgedb-client", 3 | "version": "0.0.1", 4 | "type": "commonjs" 5 | } 6 | -------------------------------------------------------------------------------- /libs/api/trpc-server-edgedb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@conference-demos/api/trpc-server-edgedb", 3 | "version": "0.0.1", 4 | "type": "commonjs" 5 | } 6 | -------------------------------------------------------------------------------- /libs/database/edgedb-client/src/lib/client.ts: -------------------------------------------------------------------------------- 1 | import * as edgedb from 'edgedb' 2 | 3 | export const client = edgedb.createClient({ 4 | logging: true, 5 | }) 6 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@conference-demos/frontend-mobile-app", 3 | "version": "0.0.1", 4 | "main": "src/index.ts" 5 | } 6 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/features/movie/detail-screen/stringifyMovieId.tsx: -------------------------------------------------------------------------------- 1 | export function stringifyMovieId(value: number) { 2 | return value.toString() 3 | } 4 | -------------------------------------------------------------------------------- /libs/api/trpc-server-edgedb/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nrwl/web/babel", 5 | { 6 | "useBuiltIns": "usage" 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /libs/database/edgedb-client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nrwl/web/babel", 5 | { 6 | "useBuiltIns": "usage" 7 | } 8 | ] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /apps/mobile-e2e/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@nrwl/react/babel", 5 | { 6 | "runtime": "automatic" 7 | } 8 | ] 9 | ], 10 | "plugins": [] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "firsttris.vscode-jest-runner", 6 | "dbaeumer.vscode-eslint" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /apps/mobile/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | plugins: ['react-native-reanimated/plugin'], 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /apps/next-app/index.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | declare module '*.svg' { 3 | const content: any; 4 | export const ReactComponent: any; 5 | export default content; 6 | } 7 | -------------------------------------------------------------------------------- /apps/mobile-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.e2e.json" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /libs/database/edgedb-client/src/index.ts: -------------------------------------------------------------------------------- 1 | import defaultExport from './lib/codegen'; 2 | export * from './lib/client' 3 | export * from './lib/edgedb-client' 4 | export * from './lib/codegen' 5 | export const e = defaultExport 6 | -------------------------------------------------------------------------------- /apps/mobile/src/app/icons/chevron-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/next-app/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /libs/frontend/trpc-client/src/lib/trpc-client.spec.ts: -------------------------------------------------------------------------------- 1 | import { trpcClient } from './trpc-client'; 2 | 3 | describe('trpcClient', () => { 4 | it('should work', () => { 5 | expect(trpcClient()).toEqual('trpc-client'); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/README.md: -------------------------------------------------------------------------------- 1 | # frontend-mobile-app 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test frontend-mobile-app` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/features/movie/detail-screen/parseMovieId.tsx: -------------------------------------------------------------------------------- 1 | export function parseMovieId(id: string | string[] | undefined) { 2 | if (!id) return 0 3 | 4 | if (Array.isArray(id)) return 0 5 | 6 | return parseInt(id, 10) 7 | } 8 | -------------------------------------------------------------------------------- /libs/api/trpc-server-edgedb/src/index.ts: -------------------------------------------------------------------------------- 1 | export { appRouter } from './lib/router' 2 | export type { AppRouter } from './lib/router' 3 | export { createContext } from './lib/context' 4 | export * from './lib/MoviesSchema' 5 | export * from './lib/trpc-helper' 6 | -------------------------------------------------------------------------------- /apps/next-app-e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /apps/next-app/pages/index.tsx: -------------------------------------------------------------------------------- 1 | export function Index() { 2 | /* 3 | * Replace the elements below with your own. 4 | * 5 | * Note: The corresponding styles are in the ./index.css file. 6 | */ 7 | return
hello trpc
8 | } 9 | 10 | export default Index; 11 | -------------------------------------------------------------------------------- /apps/mobile/src/app/icons/terminal.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/frontend/trpc-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/mobile-e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["node", "jest", "detox"] 8 | }, 9 | "include": ["src/**/*.ts", "src/**/*.js"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/next-app-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["cypress", "node"] 8 | }, 9 | "include": ["src/**/*.ts", "src/**/*.js"] 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /dbschema/migrations/00003.edgeql: -------------------------------------------------------------------------------- 1 | CREATE MIGRATION m1mmqv3hkzvt7bbdb3zguv2t3t4fffzcktvjvfj5cmtdghwgprkbia 2 | ONTO m12zsfbmdex4f7rbpkzdgwd3pwrygt3ivofmd52jw4hk55x5oxf3nq 3 | { 4 | ALTER TYPE default::User { 5 | ALTER LINK watchList { 6 | RESET ON TARGET DELETE; 7 | }; 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /dbschema/migrations/00005.edgeql: -------------------------------------------------------------------------------- 1 | CREATE MIGRATION m1dkmumathom2dvjzz33v6kzlozzo4ti47wi2ruvbbui6l3qlhr2da 2 | ONTO m17t62wnp52hxp5mfnpso2wkx74y7vjm3giykplvibywjyhe6oimbq 3 | { 4 | ALTER TYPE default::User { 5 | ALTER LINK watchList { 6 | ON TARGET DELETE ALLOW; 7 | }; 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /libs/api/trpc-server-edgedb/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /libs/database/edgedb-client/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/mobile/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["node"], 6 | }, 7 | 8 | "exclude": ["**/*.spec.ts", "**/*.spec.tsx", "test-setup.ts"], 9 | "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"] 10 | } 11 | -------------------------------------------------------------------------------- /dbschema/migrations/00002.edgeql: -------------------------------------------------------------------------------- 1 | CREATE MIGRATION m12zsfbmdex4f7rbpkzdgwd3pwrygt3ivofmd52jw4hk55x5oxf3nq 2 | ONTO m1lhwnequj3gz3a5k5qy224mmmguvahqbxymup2f2fruhb7s4h3g6a 3 | { 4 | ALTER TYPE default::User { 5 | ALTER LINK watchList { 6 | ON TARGET DELETE DELETE SOURCE; 7 | }; 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /dbschema/migrations/00004.edgeql: -------------------------------------------------------------------------------- 1 | CREATE MIGRATION m17t62wnp52hxp5mfnpso2wkx74y7vjm3giykplvibywjyhe6oimbq 2 | ONTO m1mmqv3hkzvt7bbdb3zguv2t3t4fffzcktvjvfj5cmtdghwgprkbia 3 | { 4 | ALTER TYPE default::User { 5 | ALTER LINK watchList { 6 | ON TARGET DELETE DELETE SOURCE; 7 | }; 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /libs/api/trpc-server-edgedb/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "declaration": true, 6 | "types": [] 7 | }, 8 | "include": ["**/*.ts"], 9 | "exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /libs/database/edgedb-client/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "declaration": true, 6 | "types": [] 7 | }, 8 | "include": ["**/*.ts"], 9 | "exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/mobile/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'mobile', 4 | preset: '../../jest.preset.js', 5 | transform: { 6 | '^.+\\.[tj]sx?$': 'babel-jest', 7 | }, 8 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 9 | coverageDirectory: '../../coverage/apps/mobile', 10 | }; 11 | -------------------------------------------------------------------------------- /apps/mobile/src/app/App.spec.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { render } from '@testing-library/react-native'; 3 | 4 | import App from './App'; 5 | 6 | test('renders correctly', () => { 7 | const { getByTestId } = render(); 8 | expect(getByTestId('heading')).toHaveTextContent('Welcome'); 9 | }); 10 | -------------------------------------------------------------------------------- /libs/database/edgedb-client/README.md: -------------------------------------------------------------------------------- 1 | # edgedb-client 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Building 6 | 7 | Run `nx build edgedb-client` to build the library. 8 | 9 | ## Running unit tests 10 | 11 | Run `nx test edgedb-client` to execute the unit tests via [Jest](https://jestjs.io). 12 | -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /apps/mobile/src/app/icons/heart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dbschema/migrations/00007.edgeql: -------------------------------------------------------------------------------- 1 | CREATE MIGRATION m1iymnmz6qvqvs3kkxn6xi2gs4tdmeephzliznocat5nsjsgurcm5a 2 | ONTO m16io3iwyqlsurvqp4ymklkljrzp4cwjoobmfazp7fcfyghtdmaeqa 3 | { 4 | ALTER TYPE default::User { 5 | CREATE ACCESS POLICY own_user 6 | ALLOW ALL USING ((.uid ?= GLOBAL default::current_user)); 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /apps/mobile/src/app/icons/pointer.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/mobile/src/app/icons/blog.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/mobile/index.js: -------------------------------------------------------------------------------- 1 | import { registerRootComponent } from 'expo' 2 | 3 | import App from './src/app/App' 4 | 5 | // registerRootComponent calls AppRegistry.registerComponent('main', () => App); 6 | // It also ensures that whether you load the app in Expo Go or in a native build, 7 | // the environment is set up appropriately 8 | registerRootComponent(App) 9 | -------------------------------------------------------------------------------- /apps/next-app/specs/index.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | 4 | import Index from '../pages/index'; 5 | 6 | describe('Index', () => { 7 | it('should render successfully', () => { 8 | const { baseElement } = render(); 9 | expect(baseElement).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /libs/api/trpc-server-edgedb/README.md: -------------------------------------------------------------------------------- 1 | # api-trpc-server-edgedb 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Building 6 | 7 | Run `nx build api-trpc-server-edgedb` to build the library. 8 | 9 | ## Running unit tests 10 | 11 | Run `nx test api-trpc-server-edgedb` to execute the unit tests via [Jest](https://jestjs.io). 12 | -------------------------------------------------------------------------------- /libs/frontend/trpc-client/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../../../dist/out-tsc", 6 | "declaration": true, 7 | "types": ["node"] 8 | }, 9 | "exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"], 10 | "include": ["**/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "types": ["node"] 6 | }, 7 | "exclude": ["**/*.spec.ts", "**/*.spec.tsx", "test-setup.ts", "jest.config.ts"], 8 | "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx", "**/*.d.ts"], 9 | } 10 | -------------------------------------------------------------------------------- /apps/mobile-e2e/src/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { device, element, by, expect } from 'detox'; 2 | 3 | describe('Mobile', () => { 4 | beforeEach(async () => { 5 | await device.reloadReactNative(); 6 | }); 7 | 8 | it('should display welcome message', async () => { 9 | await expect(element(by.id('heading'))).toHaveText('Welcome Mobile 👋'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'frontend-mobile-app', 4 | preset: '../../../jest.preset.js', 5 | transform: { 6 | '^.+\\.[tj]sx?$': 'babel-jest', 7 | }, 8 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 9 | coverageDirectory: '../../../coverage/libs/frontend/mobile-app', 10 | }; 11 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/features/home/Home.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react-native'; 3 | 4 | import Home from './Home'; 5 | 6 | describe('Home', () => { 7 | it('should render successfully', () => { 8 | const { container } = render(); 9 | expect(container).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /libs/frontend/trpc-client/README.md: -------------------------------------------------------------------------------- 1 | # frontend-trpc-client 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test frontend-trpc-client` to execute the unit tests via [Jest](https://jestjs.io). 8 | 9 | ## Running lint 10 | 11 | Run `nx lint frontend-trpc-client` to execute the lint via [ESLint](https://eslint.org/). 12 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/navigation/Navigation.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react-native'; 3 | 4 | import Navigation from './Navigation'; 5 | 6 | describe('Navigation', () => { 7 | it('should render successfully', () => { 8 | const { container } = render(); 9 | expect(container).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/native-base/NativeBase.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react-native'; 3 | 4 | import NativeBase from './NativeBaseProvider' 5 | 6 | describe('NativeBase', () => { 7 | it('should render successfully', () => { 8 | const { container } = render(); 9 | expect(container).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /apps/mobile/assets/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /libs/api/trpc-server-edgedb/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /libs/database/edgedb-client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /libs/frontend/trpc-client/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/mobile/.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "f38fa18336efcf5d823eabc8ae6be42137a0ba80dc02a7638b3acfce6f57ceaa": true, 3 | "5f4c0a732b6325bf4071d9124d2ae67e037cb24fcc9c482ef82bea742109a3b8": true, 4 | "a33587a8984d9699cb920876e55ebfea4a60f017312da8687c7e3e8768f97eb1": true, 5 | "2a0b82777a71f08eaecaf8c8a5b62c282fb1c4e054c2f9092d15b09c93d97a23": true, 6 | "f174b754e326fc2ddf18aed31ada557c4379d02e245e10d537e9241d1e4b721f": true 7 | } 8 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/features/movie/detail-screen/DetailScreen.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from '@testing-library/react-native' 3 | 4 | import { MovieDetailScreen } from './DetailScreen' 5 | 6 | describe('DetailScreen', () => { 7 | it('should render successfully', () => { 8 | const { container } = render() 9 | expect(container).toBeTruthy() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /apps/mobile-e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /apps/next-app/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { AppProps } from 'next/app'; 2 | import Head from 'next/head' 3 | 4 | function CustomApp({ Component, pageProps }: AppProps) { 5 | return ( 6 | <> 7 | 8 | Welcome to next-app! 9 | 10 |
11 | 12 |
13 | 14 | ); 15 | } 16 | 17 | export default CustomApp; 18 | -------------------------------------------------------------------------------- /apps/mobile/src/app/icons/book.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/next-app/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'next-app', 4 | preset: '../../jest.preset.js', 5 | transform: { 6 | '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', 7 | '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/next/babel'] }], 8 | }, 9 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 10 | coverageDirectory: '../../coverage/apps/next-app', 11 | }; 12 | -------------------------------------------------------------------------------- /apps/mobile/src/app/icons/nx-cloud.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/mobile-e2e/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "../../jest.preset", 3 | "testEnvironment": "./environment", 4 | "testRunner": "jest-circus/runner", 5 | "testTimeout": 120000, 6 | "reporters": ["detox/runners/jest/streamlineReporter"], 7 | "setupFilesAfterEnv": ["/test-setup.ts"], 8 | "transform": { 9 | "^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "@nrwl/react/plugins/jest", 10 | "^.+\\.[tj]sx?$": "babel-jest" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /apps/next-app/next.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const withNx = require('@nrwl/next/plugins/with-nx'); 3 | 4 | /** 5 | * @type {import('@nrwl/next/plugins/with-nx').WithNxOptions} 6 | **/ 7 | const nextConfig = { 8 | nx: { 9 | // Set this to true if you would like to to use SVGR 10 | // See: https://github.com/gregberge/svgr 11 | svgr: false, 12 | }, 13 | }; 14 | 15 | module.exports = withNx(nextConfig); 16 | -------------------------------------------------------------------------------- /libs/database/edgedb-client/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'edgedb-client', 4 | preset: '../../../jest.preset.js', 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | }, 9 | }, 10 | transform: { 11 | '^.+\\.[tj]s$': 'ts-jest', 12 | }, 13 | moduleFileExtensions: ['ts', 'js', 'html'], 14 | coverageDirectory: '../../../coverage/libs/database/edgedb-client', 15 | }; 16 | -------------------------------------------------------------------------------- /apps/next-app-e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileServerFolder": ".", 3 | "fixturesFolder": "./src/fixtures", 4 | "integrationFolder": "./src/integration", 5 | "modifyObstructiveCode": false, 6 | "supportFile": "./src/support/index.ts", 7 | "pluginsFile": false, 8 | "video": true, 9 | "videosFolder": "../../dist/cypress/apps/next-app-e2e/videos", 10 | "screenshotsFolder": "../../dist/cypress/apps/next-app-e2e/screenshots", 11 | "chromeWebSecurity": false 12 | } 13 | -------------------------------------------------------------------------------- /libs/api/trpc-server-edgedb/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'api-trpc-server-edgedb', 4 | preset: '../../../jest.preset.js', 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | }, 9 | }, 10 | transform: { 11 | '^.+\\.[tj]s$': 'ts-jest', 12 | }, 13 | moduleFileExtensions: ['ts', 'js', 'html'], 14 | coverageDirectory: '../../../coverage/libs/api/trpc-server-edgedb', 15 | } 16 | -------------------------------------------------------------------------------- /libs/frontend/trpc-client/src/lib/trpc-client.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouter } from '@conference-demos/api/trpc-server-edgedb' 2 | import { createTRPCClient } from '@trpc/client' 3 | import { createReactQueryHooks } from '@trpc/react' 4 | // import superjson from 'superjson'; 5 | 6 | export const client = createTRPCClient({ 7 | url: process.env.API_ENDPOINT, 8 | // transformer: superjson, 9 | }) 10 | 11 | export const trpc = createReactQueryHooks() 12 | -------------------------------------------------------------------------------- /apps/mobile/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "**/*.test.ts", 11 | "**/*.spec.ts", 12 | "**/*.test.tsx", 13 | "**/*.spec.tsx", 14 | "**/*.test.js", 15 | "**/*.spec.js", 16 | "**/*.test.jsx", 17 | "**/*.spec.jsx", 18 | "**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /apps/next-app-e2e/src/integration/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po'; 2 | 3 | describe('next-app', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display welcome message', () => { 7 | // Custom command example, see `../support/commands.ts` file 8 | cy.login('my-email@something.com', 'myPassword'); 9 | 10 | // Function helper example, see `../support/app.po.ts` file 11 | getGreeting().contains('Welcome next-app'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /apps/mobile/src/app/icons/youtube.svg: -------------------------------------------------------------------------------- 1 | YouTube -------------------------------------------------------------------------------- /dbschema/migrations/00001.edgeql: -------------------------------------------------------------------------------- 1 | CREATE MIGRATION m1lhwnequj3gz3a5k5qy224mmmguvahqbxymup2f2fruhb7s4h3g6a 2 | ONTO initial 3 | { 4 | CREATE TYPE default::WatchListItem { 5 | CREATE REQUIRED PROPERTY movieId -> std::str; 6 | }; 7 | CREATE TYPE default::User { 8 | CREATE MULTI LINK watchList -> default::WatchListItem; 9 | CREATE PROPERTY email -> std::str; 10 | CREATE PROPERTY uid -> std::str { 11 | CREATE CONSTRAINT std::exclusive; 12 | }; 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /libs/api/trpc-server-edgedb/src/lib/router.ts: -------------------------------------------------------------------------------- 1 | // src/server/router/index.ts 2 | import { createRouter } from './context'; 3 | // import superjson from 'superjson'; 4 | 5 | import { tmdbRouter } from './tmdb-router' 6 | import { userDataRouter } from './user-router' 7 | 8 | export const appRouter = createRouter() 9 | // .transformer(superjson) 10 | .merge('tmdb.', tmdbRouter) 11 | .merge('user.', userDataRouter) 12 | 13 | // export type definition of API 14 | export type AppRouter = typeof appRouter; 15 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "**/*.test.ts", 11 | "**/*.spec.ts", 12 | "**/*.test.tsx", 13 | "**/*.spec.tsx", 14 | "**/*.test.js", 15 | "**/*.spec.js", 16 | "**/*.test.jsx", 17 | "**/*.spec.jsx", 18 | "**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /libs/frontend/trpc-client/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'frontend-trpc-client', 4 | preset: '../../../jest.preset.js', 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | }, 9 | }, 10 | testEnvironment: 'node', 11 | transform: { 12 | '^.+\\.[tj]sx?$': 'ts-jest', 13 | }, 14 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 15 | coverageDirectory: '../../../coverage/libs/frontend/trpc-client', 16 | }; 17 | -------------------------------------------------------------------------------- /libs/frontend/trpc-client/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "**/*.test.ts", 11 | "**/*.spec.ts", 12 | "**/*.test.tsx", 13 | "**/*.spec.tsx", 14 | "**/*.test.js", 15 | "**/*.spec.js", 16 | "**/*.test.jsx", 17 | "**/*.spec.jsx", 18 | "**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /apps/next-app/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"], 7 | "jsx": "react" 8 | }, 9 | "include": [ 10 | "jest.config.ts", 11 | "**/*.test.ts", 12 | "**/*.spec.ts", 13 | "**/*.test.tsx", 14 | "**/*.spec.tsx", 15 | "**/*.test.js", 16 | "**/*.spec.js", 17 | "**/*.test.jsx", 18 | "**/*.spec.jsx", 19 | "**/*.d.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /apps/mobile/src/app/icons/vscode.svg: -------------------------------------------------------------------------------- 1 | Visual Studio Code -------------------------------------------------------------------------------- /dbschema/migrations/00006.edgeql: -------------------------------------------------------------------------------- 1 | CREATE MIGRATION m16io3iwyqlsurvqp4ymklkljrzp4cwjoobmfazp7fcfyghtdmaeqa 2 | ONTO m1dkmumathom2dvjzz33v6kzlozzo4ti47wi2ruvbbui6l3qlhr2da 3 | { 4 | CREATE GLOBAL default::current_user -> std::str; 5 | ALTER TYPE default::WatchListItem { 6 | CREATE LINK user -> default::User; 7 | CREATE ACCESS POLICY own_watchlist 8 | ALLOW ALL USING ((.user.uid ?= GLOBAL default::current_user)); 9 | }; 10 | ALTER TYPE default::User { 11 | DROP LINK watchList; 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/workspace-schema.json", 3 | "version": 2, 4 | "projects": { 5 | "api-trpc-server-edgedb": "libs/api/trpc-server-edgedb", 6 | "edgedb-client": "libs/database/edgedb-client", 7 | "frontend-mobile-app": "libs/frontend/mobile-app", 8 | "frontend-trpc-client": "libs/frontend/trpc-client", 9 | "mobile": "apps/mobile", 10 | "mobile-e2e": "apps/mobile-e2e", 11 | "next-app": "apps/next-app", 12 | "next-app-e2e": "apps/next-app-e2e" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/mobile/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*", ".expo", "node_modules", "web-build"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": { 8 | "@typescript-eslint/ban-ts-comment": "off" 9 | } 10 | }, 11 | { 12 | "files": ["*.ts", "*.tsx"], 13 | "rules": {} 14 | }, 15 | { 16 | "files": ["*.js", "*.jsx"], 17 | "rules": {} 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/features/home/ButtonLink.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from 'native-base'; 3 | import { useLink, UseLinkProps } from 'solito/link'; 4 | 5 | export function ButtonLink({ href, as, ...props }: ButtonLinkProps) { 6 | const linkProps = useLink({ 7 | href, 8 | as, 9 | }); 10 | 11 | 12 | return ( 13 | 16 | ) 17 | } 18 | type ButtonLinkProps = UseLinkProps & { children: React.ReactNode }; 19 | -------------------------------------------------------------------------------- /dbschema/default.esdl: -------------------------------------------------------------------------------- 1 | 2 | module default { 3 | global current_user -> str; 4 | 5 | type User { 6 | property uid -> str { 7 | constraint exclusive; 8 | }; 9 | property email -> str; 10 | access policy own_user 11 | allow all 12 | using (.uid ?= global current_user) 13 | } 14 | 15 | type WatchListItem { 16 | required property movieId -> str; 17 | link user -> User; 18 | access policy own_watchlist 19 | allow all 20 | using (.user.uid ?= global current_user) 21 | } 22 | 23 | }; -------------------------------------------------------------------------------- /apps/mobile/webpack.config.js: -------------------------------------------------------------------------------- 1 | const createExpoWebpackConfigAsync = require('@expo/webpack-config'); 2 | const { withNxWebpack } = require('@nrwl/expo'); 3 | 4 | module.exports = async function (env, argv) { 5 | let config = await createExpoWebpackConfigAsync(env, argv); 6 | config = await withNxWebpack(config); 7 | 8 | // You can override the config here, for example: 9 | // config.resolve.alias = { 10 | // ...config.resolve.alias, 11 | // react: path.resolve('../../node_modules/react'), 12 | // }; 13 | 14 | return config; 15 | }; 16 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:@nrwl/nx/react", "../../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*", ".expo", "node_modules", "web-build"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": { 8 | "@typescript-eslint/ban-ts-comment": "off" 9 | } 10 | }, 11 | { 12 | "files": ["*.ts", "*.tsx"], 13 | "rules": {} 14 | }, 15 | { 16 | "files": ["*.js", "*.jsx"], 17 | "rules": {} 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /apps/mobile/src/app/ReactotronConfig.ts: -------------------------------------------------------------------------------- 1 | import Reactotron, { networking } from 'reactotron-react-native' 2 | import AsyncStorage from '@react-native-async-storage/async-storage' 3 | 4 | Reactotron.setAsyncStorageHandler?.(AsyncStorage) // AsyncStorage would either come from `react-native` or `@react-native-async-storage/async-storage` depending on where you get it from 5 | .configure() // controls connection & communication settings 6 | .useReactNative() // add all built-in react native plugins 7 | .use(networking()) // <--- here we go! 8 | .connect() // let's connect! 9 | -------------------------------------------------------------------------------- /apps/mobile/src/app/icons/courses.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/api/trpc-server-edgedb/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.lib.json" 17 | }, 18 | { 19 | "path": "./tsconfig.spec.json" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /libs/database/edgedb-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.lib.json" 17 | }, 18 | { 19 | "path": "./tsconfig.spec.json" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /apps/mobile/.expo-shared/README.md: -------------------------------------------------------------------------------- 1 | > Why do I have a folder named ".expo-shared" in my project? 2 | 3 | The ".expo-shared" folder is created when running commands that produce state that is intended to be shared with all developers on the project. For example, "npx expo-optimize". 4 | 5 | > What does the "assets.json" file contain? 6 | 7 | The "assets.json" file describes the assets that have been optimized through "expo-optimize" and do not need to be processed again. 8 | 9 | > Should I commit the ".expo-shared" folder? 10 | 11 | Yes, you should share the ".expo-shared" folder with your collaborators. 12 | -------------------------------------------------------------------------------- /apps/next-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": false, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "incremental": true, 14 | "types": ["jest", "node"] 15 | }, 16 | "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "next-env.d.ts"], 17 | "exclude": ["node_modules", "jest.config.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "react-native", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true 12 | }, 13 | "files": [], 14 | "include": [], 15 | "references": [ 16 | { 17 | "path": "./tsconfig.lib.json" 18 | }, 19 | { 20 | "path": "./tsconfig.spec.json" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /apps/mobile/src/app/icons/checkmark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/mobile/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "allowSyntheticDefaultImports": true, 5 | "jsx": "react-native", 6 | "lib": ["dom", "esnext"], 7 | "moduleResolution": "node", 8 | "noEmit": true, 9 | "skipLibCheck": true, 10 | "resolveJsonModule": true, 11 | "strict": true 12 | }, 13 | "files": ["../../node_modules/@nrwl/expo/typings/svg.d.ts"], 14 | "include": [], 15 | "references": [ 16 | { 17 | "path": "./tsconfig.app.json" 18 | }, 19 | { 20 | "path": "./tsconfig.spec.json" 21 | } 22 | ], 23 | "exclude": ["node_modules"] 24 | } 25 | -------------------------------------------------------------------------------- /apps/next-app/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@nrwl/nx/react-typescript", 4 | "../../.eslintrc.json", 5 | "next", 6 | "next/core-web-vitals" 7 | ], 8 | "ignorePatterns": ["!**/*"], 9 | "overrides": [ 10 | { 11 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 12 | "rules": { 13 | "@next/next/no-html-link-for-pages": ["error", "apps/next-app/pages"] 14 | } 15 | }, 16 | { 17 | "files": ["*.ts", "*.tsx"], 18 | "rules": {} 19 | }, 20 | { 21 | "files": ["*.js", "*.jsx"], 22 | "rules": {} 23 | } 24 | ], 25 | "env": { 26 | "jest": true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /apps/next-app-e2e/src/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /apps/mobile-e2e/environment.js: -------------------------------------------------------------------------------- 1 | const { 2 | DetoxCircusEnvironment, 3 | SpecReporter, 4 | WorkerAssignReporter, 5 | } = require('detox/runners/jest-circus'); 6 | 7 | class CustomDetoxEnvironment extends DetoxCircusEnvironment { 8 | constructor(config, context) { 9 | super(config, context); 10 | 11 | // Can be safely removed, if you are content with the default value (=300000ms) 12 | this.initTimeout = 300000; 13 | 14 | // This takes care of generating status logs on a per-spec basis. By default, Jest only reports at file-level. 15 | // This is strictly optional. 16 | this.registerListeners({ 17 | SpecReporter, 18 | WorkerAssignReporter, 19 | }); 20 | } 21 | } 22 | 23 | module.exports = CustomDetoxEnvironment; 24 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/providers/Provider.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import NativeBaseProvider from '../native-base/NativeBaseProvider' 3 | import { NavigationProvider } from './NavigationProvider' 4 | import { AuthenticationProvider } from './AuthenticationProvider' 5 | import { TRPCProvider } from './TRPCProvider' 6 | 7 | export interface ProviderProps { 8 | children: React.ReactNode 9 | } 10 | 11 | export function Provider({ children }: ProviderProps) { 12 | return ( 13 | 14 | 15 | 16 | {children} 17 | 18 | 19 | 20 | ) 21 | } 22 | 23 | export default Provider 24 | -------------------------------------------------------------------------------- /.github/workflows/develop.yml: -------------------------------------------------------------------------------- 1 | name: develop 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | jobs: 10 | main: 11 | name: Nx Cloud - Main Job 12 | uses: nrwl/ci/.github/workflows/nx-cloud-main.yml@v0.4 13 | with: 14 | parallel-commands: | 15 | yarn nx-cloud record -- yarn nx workspace-lint 16 | yarn nx-cloud record -- yarn nx format:check 17 | parallel-commands-on-agents: | 18 | yarn nx affected --target=lint --parallel=3 19 | yarn nx affected --target=test --parallel=3 --ci --code-coverage 20 | yarn nx affected --target=build --parallel=3 21 | 22 | agents: 23 | name: Nx Cloud - Agents 24 | uses: nrwl/ci/.github/workflows/nx-cloud-agents.yml@v0.4 25 | with: 26 | number-of-agents: 3 27 | -------------------------------------------------------------------------------- /apps/mobile/src/app/icons/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/api/trpc-server-edgedb/src/lib/MoviesSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const MovieSchema = z.object({ 4 | id: z.number(), 5 | title: z.string(), 6 | poster_path: z.string(), 7 | posterImage: z.string(), 8 | backdrop_path: z.string().nullable(), 9 | backdropImage: z.string(), 10 | release_date: z.string(), 11 | overview: z.string(), 12 | vote_average: z.number(), 13 | vote_count: z.number(), 14 | popularity: z.number(), 15 | original_language: z.string(), 16 | original_title: z.string(), 17 | genre_ids: z.array(z.number()), 18 | video: z.boolean(), 19 | adult: z.boolean(), 20 | }) 21 | export const MoviesSchema = z.object({ 22 | movies: z.array(MovieSchema), 23 | }) 24 | 25 | export type MoviesType = z.infer; 26 | export type MovieType = z.infer -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/features/home/Home.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Center, Spinner, Text } from 'native-base' 3 | import { trpc } from '@conference-demos/trpc-client' 4 | import { MovieList } from '../../navigation/MovieList' 5 | 6 | export function NowPlayingScreen() { 7 | const { data, error, isLoading } = trpc.useQuery(['tmdb.nowPlaying']) 8 | 9 | if (error) { 10 | console.log('error', error) 11 | 12 | return Error: {error.message} 13 | } 14 | 15 | if (isLoading) 16 | return ( 17 |
18 | 19 |
20 | ) 21 | if (!data) 22 | return ( 23 |
24 | 25 |
26 | ) 27 | 28 | 29 | const movieData = data.movies 30 | 31 | return 32 | } 33 | 34 | -------------------------------------------------------------------------------- /apps/mobile/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'mobile', 3 | resolver: '@nrwl/jest/plugins/resolver', 4 | preset: 'jest-expo', 5 | transformIgnorePatterns: [ 6 | 'node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)', 7 | ], 8 | moduleFileExtensions: ['ts', 'js', 'html', 'tsx', 'jsx'], 9 | setupFilesAfterEnv: ['/test-setup.ts'], 10 | moduleNameMapper: { 11 | '.svg': '@nrwl/expo/plugins/jest/svg-mock', 12 | }, 13 | transform: { 14 | '\\.(js|ts|tsx)$': require.resolve('react-native/jest/preprocessor.js'), 15 | '^.+\\.(bmp|gif|jpg|jpeg|mp4|png|psd|svg|webp|ttf)$': require.resolve( 16 | 'react-native/jest/assetFileTransformer.js' 17 | ), 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /apps/next-app-e2e/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 3 | "sourceRoot": "apps/next-app-e2e/src", 4 | "projectType": "application", 5 | "targets": { 6 | "e2e": { 7 | "executor": "@nrwl/cypress:cypress", 8 | "options": { 9 | "cypressConfig": "apps/next-app-e2e/cypress.json", 10 | "devServerTarget": "next-app:serve:development" 11 | }, 12 | "configurations": { 13 | "production": { 14 | "devServerTarget": "next-app:serve:production" 15 | } 16 | } 17 | }, 18 | "lint": { 19 | "executor": "@nrwl/linter:eslint", 20 | "outputs": ["{options.outputFile}"], 21 | "options": { 22 | "lintFilePatterns": ["apps/next-app-e2e/**/*.{js,ts}"] 23 | } 24 | } 25 | }, 26 | "tags": [], 27 | "implicitDependencies": ["next-app"] 28 | } 29 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | displayName: 'app', 3 | resolver: '@nrwl/jest/plugins/resolver', 4 | preset: 'jest-expo', 5 | transformIgnorePatterns: [ 6 | 'node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)', 7 | ], 8 | moduleFileExtensions: ['ts', 'js', 'html', 'tsx', 'jsx'], 9 | setupFilesAfterEnv: ['/test-setup.ts'], 10 | moduleNameMapper: { 11 | '.svg': '@nrwl/expo/plugins/jest/svg-mock', 12 | }, 13 | transform: { 14 | '\\.(js|ts|tsx)$': require.resolve('react-native/jest/preprocessor.js'), 15 | '^.+\\.(bmp|gif|jpg|jpeg|mp4|png|psd|svg|webp|ttf)$': require.resolve( 16 | 'react-native/jest/assetFileTransformer.js' 17 | ), 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /apps/next-app/pages/api/trpc/[trpc].ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains tRPC's HTTP response handler 3 | */ 4 | import * as trpcNext from '@trpc/server/adapters/next'; 5 | import { 6 | appRouter, 7 | createContext, 8 | } from '@conference-demos/api/trpc-server-edgedb' 9 | 10 | export default trpcNext.createNextApiHandler({ 11 | router: appRouter, 12 | /** 13 | * @link https://trpc.io/docs/context 14 | */ 15 | createContext: createContext, 16 | /** 17 | * @link https://trpc.io/docs/error-handling 18 | */ 19 | onError({ error }) { 20 | if (error.code === 'INTERNAL_SERVER_ERROR') { 21 | // send to bug reporting 22 | console.error('Something went wrong', error); 23 | } 24 | }, 25 | /** 26 | * Enable query batching 27 | */ 28 | batching: { 29 | enabled: true, 30 | }, 31 | /** 32 | * @link https://trpc.io/docs/caching#api-response-caching 33 | */ 34 | // responseMeta() { 35 | // // ... 36 | // }, 37 | }); 38 | -------------------------------------------------------------------------------- /apps/mobile/eas.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "production": { 4 | "releaseChannel": "production", 5 | "android": { 6 | "buildType": "app-bundle" 7 | }, 8 | "env": { 9 | "APP_ENV": "production" 10 | } 11 | }, 12 | "development": { 13 | "android": { 14 | "developmentClient": true, 15 | "distribution": "internal" 16 | } 17 | }, 18 | "preview": { 19 | "distribution": "internal", 20 | "releaseChannel": "staging", 21 | "ios": { 22 | "simulator": true 23 | }, 24 | "android": { 25 | "buildType": "apk" 26 | }, 27 | "env": { 28 | "APP_ENV": "staging" 29 | } 30 | }, 31 | "test": { 32 | "android": { 33 | "buildType": "apk" 34 | }, 35 | "ios": { 36 | "simulator": true 37 | }, 38 | "env": { 39 | "APP_ENV": "staging" 40 | } 41 | } 42 | }, 43 | "cli": { 44 | "version": ">= 0.38.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /apps/mobile/.gitignore: -------------------------------------------------------------------------------- 1 | ios 2 | android 3 | # @generated expo-cli sync-e7dcf75f4e856f7b6f3239b3f3a7dd614ee755a8 4 | # The following patterns were generated by expo-cli 5 | 6 | # OSX 7 | # 8 | .DS_Store 9 | 10 | # Xcode 11 | # 12 | build/ 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata 22 | *.xccheckout 23 | *.moved-aside 24 | DerivedData 25 | *.hmap 26 | *.ipa 27 | *.xcuserstate 28 | project.xcworkspace 29 | 30 | # Android/IntelliJ 31 | # 32 | build/ 33 | .idea 34 | .gradle 35 | local.properties 36 | *.iml 37 | *.hprof 38 | 39 | # node.js 40 | # 41 | node_modules/ 42 | npm-debug.log 43 | yarn-error.log 44 | 45 | # BUCK 46 | buck-out/ 47 | \.buckd/ 48 | *.keystore 49 | !debug.keystore 50 | 51 | # Bundle artifacts 52 | *.jsbundle 53 | 54 | # CocoaPods 55 | /ios/Pods/ 56 | 57 | # Expo 58 | .expo/ 59 | web-build/ 60 | dist/ 61 | 62 | # @end expo-cli 63 | 64 | google-services.json 65 | GoogleService-Info.plist -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/navigation/MovieList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Box, FlatList } from 'native-base' 3 | import { MoviePoster } from '../features/home/MoviePoster' 4 | import { MoviesType } from '@conference-demos/api/trpc-server-edgedb' 5 | import { Link } from 'solito/link' 6 | 7 | export type Movie = MoviesType['movies'][number] 8 | 9 | export function MovieList({ movieData }: { movieData: Movie[] }) { 10 | return ( 11 | 17 | { 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ) 30 | }} 31 | keyExtractor={(item) => item.id.toString()} 32 | /> 33 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nrwl/nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "rules": { 9 | "@nrwl/nx/enforce-module-boundaries": [ 10 | "error", 11 | { 12 | "enforceBuildableLibDependency": true, 13 | "allow": [], 14 | "depConstraints": [ 15 | { 16 | "sourceTag": "*", 17 | "onlyDependOnLibsWithTags": ["*"] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | }, 24 | { 25 | "files": ["*.ts", "*.tsx"], 26 | "extends": ["plugin:@nrwl/nx/typescript"], 27 | "rules": {} 28 | }, 29 | { 30 | "files": ["*.js", "*.jsx"], 31 | "extends": ["plugin:@nrwl/nx/javascript"], 32 | "rules": {} 33 | }, 34 | { 35 | "files": ["swagger.ts"], 36 | "extends": ["plugin:@nrwl/nx/typescript"], 37 | "rules": { 38 | "@typescript-eslint/no-empty-interface": "off" 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /apps/mobile/metro.config.js: -------------------------------------------------------------------------------- 1 | const { withNxMetro } = require('@nrwl/expo'); 2 | const { getDefaultConfig } = require('@expo/metro-config'); 3 | 4 | const defaultConfig = getDefaultConfig(__dirname); 5 | 6 | module.exports = (async () => { 7 | defaultConfig.transformer.babelTransformerPath = require.resolve( 8 | 'react-native-svg-transformer' 9 | ); 10 | defaultConfig.resolver.assetExts = defaultConfig.resolver.assetExts.filter( 11 | (ext) => ext !== 'svg' 12 | ); 13 | 14 | defaultConfig.resolver.sourceExts.push('svg'); 15 | defaultConfig.resolver.sourceExts.push('cjs'); 16 | 17 | return withNxMetro(defaultConfig, { 18 | // Change this to true to see debugging info. 19 | // Useful if you have issues resolving modules 20 | debug: false, 21 | // all the file extensions used for imports other than 'ts', 'tsx', 'js', 'jsx' 22 | extensions: ['cjs'], 23 | // the project root to start the metro server 24 | projectRoot: __dirname, 25 | // Specify any additional (to projectRoot) watch folders, this is used to know which files to watch 26 | watchFolders: [], 27 | }); 28 | })(); 29 | -------------------------------------------------------------------------------- /libs/api/trpc-server-edgedb/src/lib/context.ts: -------------------------------------------------------------------------------- 1 | import * as trpc from '@trpc/server' 2 | import * as trpcNext from '@trpc/server/adapters/next' 3 | import { client as defaultClient } from '@conference-demos/edgedb-client' 4 | 5 | export const createContext = async ({ 6 | req, 7 | res, 8 | }: trpcNext.CreateNextContextOptions) => { 9 | const TMDB_TOKEN = 'Bearer ' + process.env['TMDB_BEARER_TOKEN'] 10 | 11 | const response = { req, res, client: defaultClient, TMDB_TOKEN, uid: '' } 12 | 13 | const bearerToken = req.headers.authorization || '' 14 | const bearerTokenParts = bearerToken.split('Bearer ') 15 | const bearerTokenValue = bearerTokenParts[1] 16 | 17 | if (bearerTokenValue) { 18 | response.uid = bearerTokenValue 19 | 20 | // create scoped client for edgedb auth 21 | const scopedClient = defaultClient.withGlobals({ 22 | current_user: response.uid, 23 | }) 24 | 25 | response.client = scopedClient 26 | return response 27 | } 28 | 29 | return response 30 | } 31 | 32 | type Context = trpc.inferAsyncReturnType 33 | 34 | export const createRouter = () => trpc.router() 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | 41 | # Expo 42 | node_modules/ 43 | .expo/ 44 | dist/ 45 | npm-debug.* 46 | *.jks 47 | *.p8 48 | *.p12 49 | *.key 50 | *.mobileprovision 51 | *.orig.* 52 | web-build/ 53 | 54 | 55 | apps/mobile-e2e/artifacts 56 | .env 57 | 58 | serviceAccountKey.json 59 | apps/mobile/pc-api-8267020174778927726-748-81eb349346aa.json 60 | tmdb-watchlist-8db00-firebase-adminsdk-h6fmf-7cf0af9244.json 61 | apps/mobile/google-services.prod.json 62 | apps/mobile/GoogleService-Info.prod.plist 63 | -------------------------------------------------------------------------------- /libs/api/trpc-server-edgedb/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../node_modules/nx/schemas/project-schema.json", 3 | "sourceRoot": "libs/api/trpc-server-edgedb/src", 4 | "projectType": "library", 5 | "targets": { 6 | "build": { 7 | "executor": "@nrwl/js:tsc", 8 | "outputs": ["{options.outputPath}"], 9 | "options": { 10 | "outputPath": "dist/libs/api/trpc-server-edgedb", 11 | "main": "libs/api/trpc-server-edgedb/src/index.ts", 12 | "tsConfig": "libs/api/trpc-server-edgedb/tsconfig.lib.json", 13 | "assets": ["libs/api/trpc-server-edgedb/*.md"] 14 | } 15 | }, 16 | "lint": { 17 | "executor": "@nrwl/linter:eslint", 18 | "outputs": ["{options.outputFile}"], 19 | "options": { 20 | "lintFilePatterns": ["libs/api/trpc-server-edgedb/**/*.ts"] 21 | } 22 | }, 23 | "test": { 24 | "executor": "@nrwl/jest:jest", 25 | "outputs": ["coverage/libs/api/trpc-server-edgedb"], 26 | "options": { 27 | "jestConfig": "libs/api/trpc-server-edgedb/jest.config.ts", 28 | "passWithNoTests": true 29 | } 30 | } 31 | }, 32 | "tags": [] 33 | } 34 | -------------------------------------------------------------------------------- /libs/database/edgedb-client/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../node_modules/nx/schemas/project-schema.json", 3 | "sourceRoot": "libs/database/edgedb-client/src", 4 | "projectType": "library", 5 | "targets": { 6 | "build": { 7 | "executor": "@nrwl/js:tsc", 8 | "outputs": ["{options.outputPath}"], 9 | "options": { 10 | "outputPath": "dist/libs/database/edgedb-client", 11 | "main": "libs/database/edgedb-client/src/index.ts", 12 | "tsConfig": "libs/database/edgedb-client/tsconfig.lib.json", 13 | "assets": ["libs/database/edgedb-client/*.md"] 14 | } 15 | }, 16 | "lint": { 17 | "executor": "@nrwl/linter:eslint", 18 | "outputs": ["{options.outputFile}"], 19 | "options": { 20 | "lintFilePatterns": ["libs/database/edgedb-client/**/*.ts"] 21 | } 22 | }, 23 | "test": { 24 | "executor": "@nrwl/jest:jest", 25 | "outputs": ["coverage/libs/database/edgedb-client"], 26 | "options": { 27 | "jestConfig": "libs/database/edgedb-client/jest.config.ts", 28 | "passWithNoTests": true 29 | } 30 | } 31 | }, 32 | "tags": [] 33 | } 34 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/navigation/Navigation.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createNativeStackNavigator, 3 | NativeStackScreenProps, 4 | } from '@react-navigation/native-stack' 5 | import { MovieDetailScreen } from '../features/movie/detail-screen/DetailScreen' 6 | import React from 'react' 7 | import { NavigatorScreenParams } from '@react-navigation/native' 8 | import { HomeStackTabs, TabNavigation } from './TabStack' 9 | 10 | export type WatchlistStackParams = { 11 | HomeStack: NavigatorScreenParams 12 | Details: { 13 | id: string 14 | } 15 | } 16 | 17 | export type WatchlistMovieScreenNavigationProps = NativeStackScreenProps< 18 | WatchlistStackParams, 19 | 'Details' 20 | > 21 | 22 | const Stack = createNativeStackNavigator() 23 | 24 | export function WatchlistStack() { 25 | return ( 26 | 27 | 34 | 35 | 36 | ) 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /apps/mobile/src/app/App.tsx: -------------------------------------------------------------------------------- 1 | import { LogBox } from 'react-native' 2 | import React from 'react' 3 | import { Provider, RootNavigator } from '@conference-demos/frontend-mobile-app' 4 | import * as SplashScreen from 'expo-splash-screen' 5 | if (__DEV__) { 6 | // debugging with Reactotron 7 | import('./ReactotronConfig').then(() => console.log('Reactotron Configured')) 8 | } 9 | 10 | export default function App() { 11 | React.useEffect(() => { 12 | async function prepare() { 13 | try { 14 | // Keep the splash screen visible while we fetch resources 15 | await SplashScreen.preventAutoHideAsync() 16 | 17 | // Artificially delay for two seconds to simulate a slow loading 18 | // experience. Please remove this if you copy and paste the code! 19 | await new Promise((resolve) => setTimeout(resolve, 2000)) 20 | } catch (e) { 21 | console.warn(e) 22 | } 23 | } 24 | 25 | prepare() 26 | }, []) 27 | 28 | return ( 29 | 30 | 31 | 32 | ) 33 | } 34 | 35 | LogBox.ignoreLogs([ 36 | "Can't perform a React state update on an unmounted component", 37 | ]) 38 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "npmScope": "conference-demos", 4 | "affected": { 5 | "defaultBase": "main" 6 | }, 7 | "implicitDependencies": { 8 | "package.json": { 9 | "dependencies": "*", 10 | "devDependencies": "*" 11 | }, 12 | ".eslintrc.json": "*" 13 | }, 14 | "tasksRunnerOptions": { 15 | "default": { 16 | "runner": "@nrwl/nx-cloud", 17 | "options": { 18 | "cacheableOperations": ["build", "lint", "test", "e2e"] 19 | } 20 | } 21 | }, 22 | "targetDefaults": { 23 | "build": { 24 | "dependsOn": ["^build"] 25 | } 26 | }, 27 | "defaultProject": "mobile", 28 | "generators": { 29 | "@nrwl/react": { 30 | "application": { 31 | "style": "none", 32 | "linter": "eslint", 33 | "babel": true 34 | }, 35 | "component": { 36 | "style": "none" 37 | }, 38 | "library": { 39 | "style": "none", 40 | "linter": "eslint" 41 | } 42 | }, 43 | "@nrwl/next": { 44 | "application": { 45 | "style": "css", 46 | "linter": "eslint" 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /libs/frontend/trpc-client/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../node_modules/nx/schemas/project-schema.json", 3 | "sourceRoot": "libs/frontend/trpc-client/src", 4 | "projectType": "library", 5 | "targets": { 6 | "build": { 7 | "executor": "@nrwl/js:tsc", 8 | "outputs": ["{options.outputPath}"], 9 | "options": { 10 | "outputPath": "dist/libs/frontend/trpc-client", 11 | "tsConfig": "libs/frontend/trpc-client/tsconfig.lib.json", 12 | "packageJson": "libs/frontend/trpc-client/package.json", 13 | "main": "libs/frontend/trpc-client/src/index.ts", 14 | "assets": ["libs/frontend/trpc-client/*.md"] 15 | } 16 | }, 17 | "lint": { 18 | "executor": "@nrwl/linter:eslint", 19 | "outputs": ["{options.outputFile}"], 20 | "options": { 21 | "lintFilePatterns": ["libs/frontend/trpc-client/**/*.ts"] 22 | } 23 | }, 24 | "test": { 25 | "executor": "@nrwl/jest:jest", 26 | "outputs": ["coverage/libs/frontend/trpc-client"], 27 | "options": { 28 | "jestConfig": "libs/frontend/trpc-client/jest.config.ts", 29 | "passWithNoTests": true 30 | } 31 | } 32 | }, 33 | "tags": [] 34 | } 35 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "lib": ["es2017", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "@conference-demos/api/trpc-server-edgedb": [ 19 | "libs/api/trpc-server-edgedb/src/index.ts" 20 | ], 21 | "@conference-demos/edgedb-client": [ 22 | "libs/database/edgedb-client/src/index.ts" 23 | ], 24 | "@conference-demos/frontend-mobile-app": [ 25 | "libs/frontend/mobile-app/src/index.ts" 26 | ], 27 | "@conference-demos/prisma-client": [ 28 | "libs/database/prisma-client/src/index.ts" 29 | ], 30 | "@conference-demos/trpc-client": [ 31 | "libs/frontend/trpc-client/src/index.ts" 32 | ], 33 | "@conference-demos/trpc-server": ["libs/api/trpc-server/src/index.ts"] 34 | } 35 | }, 36 | "exclude": ["node_modules", "tmp"] 37 | } 38 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/navigation/MyWatchlist.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Center, Spinner, Text } from 'native-base' 3 | import { trpc } from '@conference-demos/trpc-client' 4 | import { MovieList } from './MovieList' 5 | import { TextLink } from 'solito/link' 6 | 7 | export function MyWatchlist() { 8 | const { data, error, isLoading } = trpc.useQuery(['user.watchlist']) 9 | 10 | if (error) { 11 | console.log('error', error) 12 | 13 | return Error: {error.message} 14 | } 15 | 16 | if (isLoading) 17 | return ( 18 |
19 | 20 |
21 | ) 22 | if (!data) 23 | return ( 24 |
25 | 26 |
27 | ) 28 | 29 | const movieData = data.movies 30 | 31 | // show nothing if no movies 32 | if (!movieData.length) 33 | return ( 34 |
35 | 36 | No movies to show. Find a movie in{' '} 37 | 38 | Now Playing 39 | 40 | 41 |
42 | ) 43 | 44 | return 45 | } 46 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/providers/AuthenticationProvider.tsx: -------------------------------------------------------------------------------- 1 | import React, { Dispatch, SetStateAction } from 'react' 2 | 3 | /* eslint-disable-next-line */ 4 | export interface AuthenticationProviderProps { 5 | children: React.ReactNode 6 | } 7 | 8 | type UserDataType = { 9 | hasUser: boolean 10 | username: string 11 | } 12 | export const AuthenticatedUserContext = React.createContext< 13 | | { 14 | userData: UserDataType 15 | setUser: Dispatch> 16 | } 17 | | undefined 18 | >(undefined) 19 | 20 | export function AuthenticationProvider({ 21 | children, 22 | }: AuthenticationProviderProps) { 23 | const [userData, setUser] = React.useState({ hasUser: false, username: '' }) 24 | 25 | return ( 26 | 32 | {children} 33 | 34 | ) 35 | } 36 | 37 | export const useAuthenticatedUser = () => { 38 | const context = React.useContext(AuthenticatedUserContext) 39 | 40 | if (context === undefined) { 41 | throw new Error( 42 | 'useAuthenticatedUser must be used within a AuthenticationProvider' 43 | ) 44 | } 45 | 46 | return context 47 | } 48 | -------------------------------------------------------------------------------- /apps/next-app-e2e/src/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-namespace 12 | declare namespace Cypress { 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | interface Chainable { 15 | login(email: string, password: string): void; 16 | } 17 | } 18 | // 19 | // -- This is a parent command -- 20 | Cypress.Commands.add('login', (email, password) => { 21 | console.log('Custom command example: Login', email, password); 22 | }); 23 | // 24 | // -- This is a child command -- 25 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 26 | // 27 | // 28 | // -- This is a dual command -- 29 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 30 | // 31 | // 32 | // -- This will overwrite an existing command -- 33 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 34 | -------------------------------------------------------------------------------- /libs/database/edgedb-client/src/lib/edgedb-client.ts: -------------------------------------------------------------------------------- 1 | import e from './codegen' 2 | import * as edgedb from 'edgedb' 3 | 4 | export async function upsertUser(client: edgedb.Client) { 5 | const upsertQuery = e.insert(e.User, { 6 | uid: e.global.current_user, 7 | }) 8 | 9 | return upsertQuery.run(client) 10 | } 11 | 12 | export async function createWatchlistItem( 13 | data: { 14 | movieId: string 15 | }, 16 | client: edgedb.Client 17 | ) { 18 | const insertQuery = e.insert(e.WatchListItem, { 19 | movieId: data.movieId, 20 | user: e.select(e.User).assert_single(), 21 | }) 22 | 23 | const result = await insertQuery.run(client) 24 | 25 | return result 26 | } 27 | 28 | export async function deleteWatchlistItem( 29 | data: { id: string }, 30 | client: edgedb.Client 31 | ) { 32 | const deletion = e.delete(e.WatchListItem, (watchListItem) => ({ 33 | filter: e.op(watchListItem.id, '=', e.uuid(data.id)), 34 | })) 35 | 36 | const result = await deletion.run(client) 37 | 38 | return result 39 | } 40 | 41 | // get watchlist 42 | export async function getWatchlist(client: edgedb.Client) { 43 | const query = e.select(e.WatchListItem, () => ({ 44 | id: true, 45 | movieId: true, 46 | })) 47 | 48 | const result = await query.run(client) 49 | 50 | return result 51 | } 52 | -------------------------------------------------------------------------------- /apps/mobile/app.config.js: -------------------------------------------------------------------------------- 1 | import 'dotenv/config' 2 | 3 | let Config = { 4 | apiUrl: process.env.API_ENDPOINT, 5 | } 6 | 7 | if (process.env.APP_ENV === 'production') { 8 | Config.apiUrl = 'https://conference-demos.vercel.app' 9 | } else if (process.env.APP_ENV === 'staging') { 10 | Config.apiUrl = 'https://conference-demos-git-develop-mwarger.vercel.app' 11 | } 12 | 13 | export default { 14 | expo: { 15 | owner: 'mwarger', 16 | name: 'Mobile', 17 | slug: 'mobile', 18 | scheme: 'com.tmdb.watchlist.edgedb', 19 | version: '1.0.0', 20 | orientation: 'portrait', 21 | icon: './assets/icon.png', 22 | splash: { 23 | image: './assets/splash.png', 24 | resizeMode: 'contain', 25 | backgroundColor: '#f44336', 26 | }, 27 | updates: { 28 | fallbackToCacheTimeout: 0, 29 | }, 30 | assetBundlePatterns: ['**/*'], 31 | ios: { 32 | supportsTablet: true, 33 | bundleIdentifier: 'com.tmdb.watchlist.edgedb', 34 | }, 35 | android: { 36 | adaptiveIcon: { 37 | foregroundImage: './assets/adaptive-icon.png', 38 | backgroundColor: '#f44336', 39 | }, 40 | package: 'com.tmdb.watchlist.edgedb', 41 | }, 42 | web: { 43 | favicon: './assets/favicon.png', 44 | }, 45 | extra: { 46 | ...Config, 47 | }, 48 | }, 49 | } 50 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/providers/NavigationProvider.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | NavigationContainer, 3 | DefaultTheme, 4 | DarkTheme, 5 | LinkingOptions, 6 | } from '@react-navigation/native' 7 | import * as Linking from 'expo-linking' 8 | import { useMemo } from 'react' 9 | import { parseMovieId } from '../features/movie/detail-screen/parseMovieId' 10 | import { stringifyMovieId } from '../features/movie/detail-screen/stringifyMovieId' 11 | 12 | import { useColorMode } from 'native-base' 13 | import { WatchlistStackParams } from '../navigation/Navigation' 14 | 15 | const linking: LinkingOptions = { 16 | prefixes: [Linking.createURL('/')], 17 | config: { 18 | screens: { 19 | HomeStack: { 20 | screens: { 21 | NowPlaying: 'nowPlaying', 22 | MyWatchlist: 'watchlist', 23 | }, 24 | }, 25 | Details: { 26 | parse: { 27 | id: parseMovieId, 28 | }, 29 | stringify: { id: stringifyMovieId }, 30 | path: 'movie/:id', 31 | }, 32 | }, 33 | }, 34 | } 35 | 36 | export function NavigationProvider({ 37 | children, 38 | }: { 39 | children: React.ReactNode 40 | }) { 41 | const { colorMode } = useColorMode() 42 | return ( 43 | linking, [])} 46 | > 47 | {children} 48 | 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../node_modules/nx/schemas/project-schema.json", 3 | "sourceRoot": "libs/frontend/mobile-app/src", 4 | "projectType": "library", 5 | "tags": [], 6 | "targets": { 7 | "build": { 8 | "executor": "@nrwl/web:rollup", 9 | "outputs": ["{options.outputPath}"], 10 | "options": { 11 | "outputPath": "dist/libs/frontend/mobile-app", 12 | "tsConfig": "libs/frontend/mobile-app/tsconfig.lib.json", 13 | "project": "libs/frontend/mobile-app/package.json", 14 | "entryFile": "libs/frontend/mobile-app/src/index.ts", 15 | "external": ["react/jsx-runtime"], 16 | "rollupConfig": "@nrwl/react/plugins/bundle-rollup", 17 | "assets": [ 18 | { 19 | "glob": "libs/frontend/mobile-app/README.md", 20 | "input": ".", 21 | "output": "." 22 | } 23 | ] 24 | } 25 | }, 26 | "lint": { 27 | "executor": "@nrwl/linter:eslint", 28 | "outputs": ["{options.outputFile}"], 29 | "options": { 30 | "lintFilePatterns": ["libs/frontend/mobile-app/**/*.{ts,tsx,js,jsx}"] 31 | } 32 | }, 33 | "test": { 34 | "executor": "@nrwl/jest:jest", 35 | "outputs": ["coverage/libs/frontend/mobile-app"], 36 | "options": { 37 | "jestConfig": "libs/frontend/mobile-app/jest.config.ts", 38 | "passWithNoTests": true 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tmdb-watchlist-edgedb 2 | 3 | This app uses TMDB to retrieve a list of Now Playing movies. You can add/remove movies to track which ones you've watched. There's a "fake" auth flow as well to show navigation as well as tRPC middleware. 4 | 5 | Simple app that showcases the following technology. 6 | 7 | Expo + tRPC + EdgeDB + NextJS + Nx + zod + react-hook-form + solito 8 | 9 | ## Setup 10 | 11 | Add your database and TMDB bearer token (instructions [here](https://www.themoviedb.org/documentation/api?language=en-US)) to the `.env` file. 12 | 13 | This also showcases access control for the user's watchlist items! 14 | 15 | Install EdgeDB for your system - https://www.edgedb.com/install 16 | 17 | Run `edgedb project init` in the root of the project to setup the existing schema in a new instance. 18 | 19 | Run `yarn edgedb:codegen` to generate the TypeScript client library. 20 | 21 | To check out your data, run, run `edgedb ui` and check it out! 22 | 23 | To be able to see your data, make sure to add the UID whose data you'd like to see to the globals section at the top of the page. 24 | 25 | 26 | 27 | ## Running the App 28 | 29 | ### Run the API 30 | 31 | `nx serve next-app` 32 | 33 | ### Run the App 34 | 35 | `nx run-ios mobile` 36 | 37 | ### Screenshots 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/providers/TRPCProvider.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useState } from 'react' 2 | import { QueryClient, QueryClientProvider } from 'react-query' 3 | import { trpc } from '@conference-demos/trpc-client' 4 | import { ProviderProps } from './Provider' 5 | import reactotron from 'reactotron-react-native' 6 | 7 | import Constants from 'expo-constants' 8 | import { useAuthenticatedUser } from './AuthenticationProvider' 9 | 10 | const { manifest } = Constants 11 | let apiHost = '' 12 | const endpoint = Constants.manifest?.extra?.apiUrl ?? '' 13 | reactotron.log?.('endpoint', endpoint) 14 | 15 | if (__DEV__) { 16 | apiHost = 17 | typeof manifest?.packagerOpts === `object` && manifest.packagerOpts.dev 18 | ? manifest.debuggerHost?.split(`:`).shift()?.concat(`:4200`) 19 | : endpoint 20 | 21 | // add http if not present 22 | if (!apiHost.startsWith('http')) { 23 | apiHost = `http://${apiHost}` 24 | } 25 | } else { 26 | apiHost = endpoint 27 | } 28 | 29 | export function TRPCProvider({ children }: ProviderProps) { 30 | const { userData } = useAuthenticatedUser() 31 | const [queryClient] = useState(() => new QueryClient()) 32 | const trpcClient = useMemo(() => { 33 | return trpc.createClient({ 34 | url: `${apiHost}/api/trpc`, 35 | 36 | // optional 37 | headers() { 38 | const idToken = userData.username 39 | 40 | return { 41 | authorization: `Bearer ${idToken}`, 42 | } 43 | }, 44 | }) 45 | }, [userData.username]) 46 | 47 | return ( 48 | 49 | {children} 50 | 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /apps/next-app/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 3 | "sourceRoot": "apps/next-app", 4 | "projectType": "application", 5 | "targets": { 6 | "build": { 7 | "executor": "@nrwl/next:build", 8 | "outputs": ["{options.outputPath}"], 9 | "defaultConfiguration": "production", 10 | "options": { 11 | "root": "apps/next-app", 12 | "outputPath": "dist/apps/next-app" 13 | }, 14 | "configurations": { 15 | "development": {}, 16 | "production": {} 17 | } 18 | }, 19 | "serve": { 20 | "executor": "@nrwl/next:server", 21 | "defaultConfiguration": "development", 22 | "options": { 23 | "buildTarget": "next-app:build", 24 | "dev": true 25 | }, 26 | "configurations": { 27 | "development": { 28 | "buildTarget": "next-app:build:development", 29 | "dev": true 30 | }, 31 | "production": { 32 | "buildTarget": "next-app:build:production", 33 | "dev": false 34 | } 35 | } 36 | }, 37 | "export": { 38 | "executor": "@nrwl/next:export", 39 | "options": { 40 | "buildTarget": "next-app:build:production" 41 | } 42 | }, 43 | "test": { 44 | "executor": "@nrwl/jest:jest", 45 | "outputs": ["coverage/apps/next-app"], 46 | "options": { 47 | "jestConfig": "apps/next-app/jest.config.ts", 48 | "passWithNoTests": true 49 | } 50 | }, 51 | "lint": { 52 | "executor": "@nrwl/linter:eslint", 53 | "outputs": ["{options.outputFile}"], 54 | "options": { 55 | "lintFilePatterns": ["apps/next-app/**/*.{ts,tsx,js,jsx}"] 56 | } 57 | } 58 | }, 59 | "tags": [] 60 | } 61 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/navigation/RootNavigator.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useAuthenticatedUser } from '../providers/AuthenticationProvider' 3 | import { Center, Spinner, View } from 'native-base' 4 | import { AuthStack } from './AuthStack' 5 | import { WatchlistStack } from './Navigation' 6 | import * as SplashScreen from 'expo-splash-screen' 7 | import reactotron from 'reactotron-react-native' 8 | import { trpc } from '@conference-demos/trpc-client' 9 | 10 | export function RootNavigator() { 11 | reactotron.log?.('RootNavigator') 12 | 13 | const syncAccount = trpc.useMutation(['user.syncAccount'], {}).mutateAsync 14 | const { userData } = useAuthenticatedUser() 15 | const [isLoading, setIsLoading] = React.useState(true) 16 | 17 | React.useEffect(() => { 18 | async function syncUser() { 19 | try { 20 | await syncAccount() 21 | } catch (error) { 22 | console.log('syncUser error', error) 23 | } finally { 24 | setIsLoading(false) 25 | } 26 | } 27 | 28 | if (userData.hasUser && userData.username) { 29 | syncUser() 30 | } else { 31 | setIsLoading(false) 32 | } 33 | }, [syncAccount, userData.hasUser, userData.username]) 34 | 35 | const onLayoutRootView = React.useCallback(async () => { 36 | if (!isLoading) { 37 | await SplashScreen.hideAsync() 38 | } 39 | }, [isLoading]) 40 | 41 | if (isLoading) { 42 | return ( 43 |
44 | 45 |
46 | ) 47 | } 48 | 49 | return ( 50 | 56 | {userData?.hasUser ? : } 57 | 58 | ) 59 | } 60 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/features/home/MoviePoster.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Image, Text, AspectRatio, ScrollView } from 'native-base' 3 | import { Movie } from '../../navigation/MovieList' 4 | 5 | export function MoviePoster({ 6 | item, 7 | showOverview, 8 | }: { 9 | item: Movie 10 | showOverview?: boolean 11 | }) { 12 | // const route = useRoute() 13 | 14 | return ( 15 | 16 | 22 | {`${item.title} 31 | 32 | 43 | {item.title} 44 | 45 | {showOverview ? ( 46 | 56 | {item.overview} 57 | 58 | ) : null} 59 | 67 | {item.release_date 68 | ? `Released: ${new Date(item.release_date).toLocaleDateString()}` 69 | : 'No date scheduled.'} 70 | 71 | 72 | ) 73 | } 74 | -------------------------------------------------------------------------------- /apps/mobile-e2e/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 3 | "sourceRoot": "apps/mobile-e2e/src", 4 | "projectType": "application", 5 | "targets": { 6 | "build-ios": { 7 | "executor": "@nrwl/detox:build", 8 | "options": { 9 | "detoxConfiguration": "ios.sim.debug" 10 | }, 11 | "configurations": { 12 | "production": { 13 | "detoxConfiguration": "ios.sim.release" 14 | } 15 | } 16 | }, 17 | "test-ios": { 18 | "executor": "@nrwl/detox:test", 19 | "options": { 20 | "detoxConfiguration": "ios.sim.debug", 21 | "buildTarget": "mobile-e2e:build-ios" 22 | }, 23 | "configurations": { 24 | "production": { 25 | "detoxConfiguration": "ios.sim.release", 26 | "buildTarget": "mobile-e2e:build-ios:prod" 27 | } 28 | } 29 | }, 30 | "build-android": { 31 | "executor": "@nrwl/detox:build", 32 | "options": { 33 | "detoxConfiguration": "android.emu.debug" 34 | }, 35 | "configurations": { 36 | "production": { 37 | "detoxConfiguration": "android.emu.release" 38 | } 39 | } 40 | }, 41 | "test-android": { 42 | "executor": "@nrwl/detox:test", 43 | "options": { 44 | "detoxConfiguration": "android.emu.debug", 45 | "buildTarget": "mobile-e2e:build-android" 46 | }, 47 | "configurations": { 48 | "production": { 49 | "detoxConfiguration": "android.emu.release", 50 | "buildTarget": "mobile-e2e:build-android:prod" 51 | } 52 | } 53 | }, 54 | "lint": { 55 | "executor": "@nrwl/linter:eslint", 56 | "outputs": ["{options.outputFile}"], 57 | "options": { 58 | "lintFilePatterns": ["apps/mobile-e2e/**/*.{ts,tsx,js,jsx}"] 59 | } 60 | } 61 | }, 62 | "tags": [], 63 | "implicitDependencies": ["mobile"] 64 | } 65 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/navigation/tmdb_logo.svg: -------------------------------------------------------------------------------- 1 | Asset 3 -------------------------------------------------------------------------------- /apps/mobile-e2e/.detoxrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "testRunner": "jest", 3 | "runnerConfig": "jest.config.json", 4 | "apps": { 5 | "ios.debug": { 6 | "type": "ios.app", 7 | "build": "cd ../mobile/ios && xcodebuild -workspace Mobile.xcworkspace -scheme Mobile -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet", 8 | "binaryPath": "../mobile/ios/build/Build/Products/Debug-iphonesimulator/Mobile.app" 9 | }, 10 | "ios.release": { 11 | "type": "ios.app", 12 | "build": "cd ../mobile/ios && xcodebuild -workspace Mobile.xcworkspace -scheme Mobile -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ./build -quiet", 13 | "binaryPath": "../mobile/ios/build/Build/Products/Release-iphonesimulator/Mobile.app" 14 | }, 15 | "android.debug": { 16 | "type": "android.apk", 17 | "build": "cd ../mobile/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug", 18 | "binaryPath": "../mobile/android/app/build/outputs/apk/debug/app-debug.apk" 19 | }, 20 | "android.release": { 21 | "type": "android.apk", 22 | "build": "cd ../mobile/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release", 23 | "binaryPath": "../mobile/android/app/build/outputs/apk/release/app-release.apk" 24 | } 25 | }, 26 | "devices": { 27 | "simulator": { 28 | "type": "ios.simulator", 29 | "device": { 30 | "type": "iPhone 13" 31 | } 32 | }, 33 | "emulator": { 34 | "type": "android.emulator", 35 | "device": { 36 | "avdName": "Pixel_4a_API_30" 37 | } 38 | } 39 | }, 40 | "configurations": { 41 | "ios.sim.release": { 42 | "device": "simulator", 43 | "app": "ios.release" 44 | }, 45 | "ios.sim.debug": { 46 | "device": "simulator", 47 | "app": "ios.debug" 48 | }, 49 | "android.emu.release": { 50 | "device": "emulator", 51 | "app": "android.release" 52 | }, 53 | "android.emu.debug": { 54 | "device": "emulator", 55 | "app": "android.debug" 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/native-base/NativeBaseProvider.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { NativeBaseProvider, Box } from 'native-base'; 4 | 5 | import { extendTheme } from 'native-base' 6 | 7 | // 1. Extend the theme 8 | const customTheme = extendTheme({ 9 | colors: { 10 | primary: { 11 | 50: '#ffe2e7', 12 | 100: '#ffb3bb', 13 | 200: '#fc8393', 14 | 300: '#f9526d', 15 | 400: '#f6224b', 16 | 500: '#dd0939', 17 | 600: '#ad0320', 18 | 700: '#7c000e', 19 | 800: '#4d0002', 20 | 900: '#200400', 21 | }, 22 | }, 23 | config: { 24 | initialColorMode: 'dark', 25 | // useSystemColorMode: true, 26 | }, 27 | components: { 28 | Button: { 29 | // Can simply pass default props to change default behaviour of components. 30 | // baseStyle: { 31 | // rounded: 'md', 32 | // _light: { 33 | // color: 'darkText', 34 | // backgroundColor: 'lightText', 35 | // }, 36 | // _dark: { 37 | // color: 'lightText', 38 | // backgroundColor: 'darkText', 39 | // }, 40 | // }, 41 | defaultProps: { 42 | colorScheme: 'red', 43 | }, 44 | }, 45 | Heading: { 46 | baseStyle: { 47 | _light: { 48 | color: 'darkText', 49 | }, 50 | _dark: { 51 | color: 'lightText', 52 | }, 53 | fontWeight: 'normal', 54 | }, 55 | // 56 | // baseStyle: ({ colorMode }: { colorMode: 'light' | 'dark' }) => { 57 | // return { 58 | // color: colorMode === 'dark' ? 'red.300' : 'blue.300', 59 | // fontWeight: 'normal', 60 | // } 61 | // }, 62 | }, 63 | }, 64 | }) 65 | 66 | // 2. Get the type of the CustomTheme 67 | type CustomThemeType = typeof customTheme 68 | 69 | // 3. Extend the internal NativeBase Theme 70 | declare module 'native-base' { 71 | type ICustomTheme = CustomThemeType 72 | } 73 | 74 | export interface NativeBaseProps { 75 | children: React.ReactNode 76 | } 77 | 78 | export function NativeBase(props: NativeBaseProps) { 79 | return ( 80 | 81 | 82 | {props.children} 83 | 84 | 85 | ) 86 | } 87 | 88 | export default NativeBase; 89 | -------------------------------------------------------------------------------- /apps/mobile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mobile", 3 | "version": "0.0.1", 4 | "private": true, 5 | "dependencies": { 6 | "@expo/metro-config": "*", 7 | "@nrwl/expo": "*", 8 | "@react-navigation/native": "*", 9 | "@testing-library/jest-native": "*", 10 | "@testing-library/react-native": "*", 11 | "@types/react": "*", 12 | "expo": "*", 13 | "expo-dev-client": "*", 14 | "expo-linking": "*", 15 | "expo-splash-screen": "*", 16 | "expo-status-bar": "*", 17 | "expo-structured-headers": "*", 18 | "expo-updates": "*", 19 | "native-base": "*", 20 | "react": "17.0.2", 21 | "react-dom": "17.0.2", 22 | "react-native": "0.68.2", 23 | "react-native-gesture-handler": "*", 24 | "react-native-reanimated": "*", 25 | "react-native-safe-area-context": "*", 26 | "react-native-screens": "*", 27 | "react-native-svg": "*", 28 | "react-native-web": "*", 29 | "solito": "*", 30 | "typescript": "*", 31 | "@react-navigation/native-stack": "*", 32 | "react-query": "*", 33 | "@trpc/server": "*", 34 | "zod": "*", 35 | "node-fetch": "*", 36 | "@trpc/client": "*", 37 | "@trpc/react": "*", 38 | "expo-web-browser": "*", 39 | "@types/react-dom": "*", 40 | "@react-navigation/bottom-tabs": "*", 41 | "edgedb": "*", 42 | "reactotron-react-native": "*", 43 | "@react-native-async-storage/async-storage": "*", 44 | "react-hook-form": "*", 45 | "@hookform/resolvers": "*" 46 | }, 47 | "scripts": { 48 | "eas-build-pre-install": "cd ../../ && cp yarn.lock ./apps/mobile/ && echo $GOOGLE_SERVICES_BASE64 && echo $GOOGLE_SERVICES_BASE64 | base64 --decode > ./apps/mobile/google-services.json && cat ./apps/mobile/google-services.json && echo $GOOGLE_SERVICE_INFO_PLIST && echo $GOOGLE_SERVICE_INFO_PLIST | base64 --decode > ./apps/mobile/GoogleService-Info.plist && cat ./apps/mobile/GoogleService-Info.plist ", 49 | "postinstall": "rm -r node_modules && cd ../../ && yarn install --ignore-engines --frozen-lockfile && npx nx sync-deps mobile && npx nx ensure-symlink mobile && cd apps/mobile && expo prebuild --no-install", 50 | "start": "expo start --dev-client", 51 | "android": "expo run:android", 52 | "ios": "expo run:ios" 53 | }, 54 | "devDependencies": { 55 | "@babel/core": "^7.12.9", 56 | "@types/react": "*", 57 | "@types/react-dom": "*", 58 | "typescript": "*" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/navigation/TabStack.tsx: -------------------------------------------------------------------------------- 1 | import { NowPlayingScreen } from '../features/home/Home' 2 | import { createBottomTabNavigator } from '@react-navigation/bottom-tabs' 3 | import React from 'react' 4 | import { MaterialCommunityIcons, MaterialIcons } from '@expo/vector-icons' 5 | import { MyWatchlist } from './MyWatchlist' 6 | import { Button, Center, Text, Box, Stack } from 'native-base' 7 | import { useAuthenticatedUser } from '../providers/AuthenticationProvider' 8 | import TMDBLogo from './tmdb_logo.svg' 9 | 10 | export type HomeStackTabs = { 11 | MyWatchlist: undefined 12 | NowPlaying: undefined 13 | Settings: undefined 14 | } 15 | const Tabs = createBottomTabNavigator() 16 | 17 | export function TabNavigation() { 18 | return ( 19 | 24 | ( 31 | 36 | ), 37 | }} 38 | /> 39 | ( 46 | 47 | ), 48 | }} 49 | /> 50 | ( 57 | 58 | ), 59 | }} 60 | /> 61 | 62 | ) 63 | } 64 | 65 | function Settings() { 66 | const { setUser } = useAuthenticatedUser() 67 | return ( 68 |
69 | 70 | 71 | API provided by: 72 | 73 | 74 | 81 | 82 |
83 | ) 84 | } 85 | -------------------------------------------------------------------------------- /libs/api/trpc-server-edgedb/src/lib/trpc-helper.ts: -------------------------------------------------------------------------------- 1 | // trpc-helper.ts 2 | import type { 3 | inferProcedureOutput, 4 | inferProcedureInput, 5 | inferSubscriptionOutput, 6 | } from '@trpc/server' 7 | import { AppRouter } from './router' 8 | 9 | /** 10 | * Enum containing all api query paths 11 | */ 12 | export type TQuery = keyof AppRouter['_def']['queries'] 13 | 14 | /** 15 | * Enum containing all api mutation paths 16 | */ 17 | export type TMutation = keyof AppRouter['_def']['mutations'] 18 | 19 | /** 20 | * Enum containing all api subscription paths 21 | */ 22 | export type TSubscription = keyof AppRouter['_def']['subscriptions'] 23 | 24 | /** 25 | * This is a helper method to infer the output of a query resolver 26 | * @example type HelloOutput = InferQueryOutput<'hello'> 27 | */ 28 | export type InferQueryOutput = inferProcedureOutput< 29 | AppRouter['_def']['queries'][TRouteKey] 30 | > 31 | 32 | /** 33 | * This is a helper method to infer the input of a query resolver 34 | * @example type HelloInput = InferQueryInput<'hello'> 35 | */ 36 | export type InferQueryInput = inferProcedureInput< 37 | AppRouter['_def']['queries'][TRouteKey] 38 | > 39 | 40 | /** 41 | * This is a helper method to infer the output of a mutation resolver 42 | * @example type HelloOutput = InferMutationOutput<'hello'> 43 | */ 44 | export type InferMutationOutput = 45 | inferProcedureOutput 46 | 47 | /** 48 | * This is a helper method to infer the input of a mutation resolver 49 | * @example type HelloInput = InferMutationInput<'hello'> 50 | */ 51 | export type InferMutationInput = 52 | inferProcedureInput 53 | 54 | /** 55 | * This is a helper method to infer the output of a subscription resolver 56 | * @example type HelloOutput = InferSubscriptionOutput<'hello'> 57 | */ 58 | export type InferSubscriptionOutput = 59 | inferProcedureOutput 60 | 61 | /** 62 | * This is a helper method to infer the asynchronous output of a subscription resolver 63 | * @example type HelloAsyncOutput = InferAsyncSubscriptionOutput<'hello'> 64 | */ 65 | export type InferAsyncSubscriptionOutput = 66 | inferSubscriptionOutput 67 | 68 | /** 69 | * This is a helper method to infer the input of a subscription resolver 70 | * @example type HelloInput = InferSubscriptionInput<'hello'> 71 | */ 72 | export type InferSubscriptionInput = 73 | inferProcedureInput 74 | -------------------------------------------------------------------------------- /apps/mobile/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 3 | "sourceRoot": "apps/mobile/src", 4 | "projectType": "application", 5 | "targets": { 6 | "build": { 7 | "executor": "nx:run-commands", 8 | "options": { 9 | "command": "echo \"Info: The 'build' target is not implemented for mobile.\"" 10 | } 11 | }, 12 | "start": { 13 | "executor": "@nrwl/expo:start", 14 | "options": { 15 | "port": 8081 16 | } 17 | }, 18 | "web": { 19 | "executor": "@nrwl/expo:start", 20 | "options": { 21 | "port": 8081, 22 | "webpack": true 23 | } 24 | }, 25 | "serve": { 26 | "executor": "@nrwl/workspace:run-commands", 27 | "options": { 28 | "command": "nx start mobile" 29 | } 30 | }, 31 | "run-ios": { 32 | "executor": "@nrwl/expo:run", 33 | "options": { 34 | "platform": "ios" 35 | } 36 | }, 37 | "run-android": { 38 | "executor": "@nrwl/expo:run", 39 | "options": { 40 | "platform": "android" 41 | } 42 | }, 43 | "build-ios": { 44 | "executor": "@nrwl/expo:build-ios", 45 | "options": {} 46 | }, 47 | "build-android": { 48 | "executor": "@nrwl/expo:build-android", 49 | "options": {} 50 | }, 51 | "build-web": { 52 | "executor": "@nrwl/expo:build-web", 53 | "options": {} 54 | }, 55 | "build-status": { 56 | "executor": "@nrwl/expo:build-web", 57 | "options": {} 58 | }, 59 | "sync-deps": { 60 | "executor": "@nrwl/expo:sync-deps", 61 | "options": {} 62 | }, 63 | "ensure-symlink": { 64 | "executor": "@nrwl/expo:ensure-symlink", 65 | "options": {} 66 | }, 67 | "publish": { 68 | "executor": "@nrwl/expo:publish", 69 | "options": {} 70 | }, 71 | "publish-set": { 72 | "executor": "@nrwl/expo:publish-set", 73 | "options": {} 74 | }, 75 | "rollback": { 76 | "executor": "@nrwl/expo:rollback", 77 | "options": {} 78 | }, 79 | "eject": { 80 | "executor": "@nrwl/expo:eject", 81 | "options": {} 82 | }, 83 | "lint": { 84 | "executor": "@nrwl/linter:eslint", 85 | "outputs": ["{options.outputFile}"], 86 | "options": { 87 | "lintFilePatterns": ["apps/mobile/**/*.{ts,tsx,js,jsx}"] 88 | } 89 | }, 90 | "test": { 91 | "executor": "@nrwl/jest:jest", 92 | "outputs": ["coverage/apps/mobile"], 93 | "options": { 94 | "jestConfig": "apps/mobile/jest.config.ts", 95 | "passWithNoTests": true 96 | } 97 | } 98 | }, 99 | "tags": [] 100 | } 101 | -------------------------------------------------------------------------------- /libs/api/trpc-server-edgedb/src/lib/user-router.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod' 2 | import { createRouter } from './context' 3 | import { TRPCError } from '@trpc/server' 4 | import { 5 | upsertUser, 6 | createWatchlistItem, 7 | deleteWatchlistItem, 8 | getWatchlist, 9 | } from '@conference-demos/edgedb-client' 10 | 11 | export const userDataRouter = createRouter() 12 | .middleware(async ({ ctx, next }) => { 13 | if (!ctx.uid) { 14 | throw new TRPCError({ code: 'UNAUTHORIZED' }) 15 | } 16 | return next() 17 | }) 18 | .mutation('syncAccount', { 19 | output: z.void(), 20 | input: z.void(), 21 | async resolve({ ctx }) { 22 | await upsertUser(ctx.client) 23 | }, 24 | }) 25 | .mutation('addToWatchlist', { 26 | output: z.void(), 27 | input: z.object({ 28 | id: z.string(), 29 | }), 30 | async resolve({ ctx, input }) { 31 | createWatchlistItem( 32 | { 33 | movieId: input.id, 34 | }, 35 | ctx.client 36 | ) 37 | }, 38 | }) 39 | .mutation('removeFromWatchlist', { 40 | output: z.void(), 41 | input: z.object({ 42 | id: z.string(), 43 | }), 44 | async resolve({ input, ctx }) { 45 | await deleteWatchlistItem({ id: input.id }, ctx.client) 46 | }, 47 | }) 48 | .query('watchlist', { 49 | input: z.void(), 50 | async resolve({ ctx }) { 51 | const watchlist = await getWatchlist(ctx.client) 52 | 53 | const watchList = watchlist ?? [] 54 | 55 | // make movie promises for each movie in watchlist 56 | const moviePromises = 57 | watchList.map(({ movieId }) => 58 | fetch(`https://api.themoviedb.org/3/movie/${movieId}`, { 59 | headers: { 60 | Authorization: ctx.TMDB_TOKEN, 61 | }, 62 | }) 63 | ) ?? [] 64 | 65 | // wait for all movie promises to resolve 66 | const moviesResults = await Promise.all(moviePromises) 67 | 68 | // get movies from results 69 | const moviesJsonPromises = moviesResults.map((response) => 70 | response.json() 71 | ) 72 | 73 | // wait for all movies to resolve 74 | const moviesJson = await Promise.all(moviesJsonPromises) 75 | 76 | const movies = moviesJson ?? [] 77 | 78 | return { 79 | movies: movies.map((movie) => { 80 | const watchListId = 81 | watchList.find( 82 | (watchlistItem) => watchlistItem.movieId === movie.id.toString() 83 | )?.id ?? '' 84 | 85 | return { 86 | ...movie, 87 | posterImage: `https://image.tmdb.org/t/p/w500/${movie.poster_path}`, 88 | backdropImage: `https://image.tmdb.org/t/p/w500/${movie.backdrop_path}`, 89 | watchListId, 90 | } 91 | }), 92 | } 93 | }, 94 | }) 95 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/navigation/AuthStack.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { createNativeStackNavigator } from '@react-navigation/native-stack' 3 | import { Button, Center, FormControl, Icon, Input, Stack } from 'native-base' 4 | import reactotron from 'reactotron-react-native' 5 | import { useAuthenticatedUser } from '../providers/AuthenticationProvider' 6 | import { MaterialIcons } from '@expo/vector-icons' 7 | import { useForm, Controller } from 'react-hook-form' 8 | import * as z from 'zod' 9 | import { zodResolver } from '@hookform/resolvers/zod' 10 | 11 | const Schema = z.object({ 12 | email: z.string().email(), 13 | }) 14 | 15 | // infer type from Schema 16 | type SchemaType = z.infer 17 | 18 | const NavStack = createNativeStackNavigator() 19 | 20 | function LoginScreen() { 21 | const { setUser } = useAuthenticatedUser() 22 | const [loading, setLoading] = React.useState(false) 23 | 24 | const { 25 | control, 26 | handleSubmit, 27 | formState: { errors }, 28 | } = useForm({ 29 | defaultValues: { 30 | email: '', 31 | }, 32 | resolver: zodResolver(Schema), 33 | }) 34 | 35 | const signIn = React.useCallback( 36 | async (data: SchemaType) => { 37 | try { 38 | setLoading(true) 39 | 40 | setUser({ hasUser: true, username: data.email }) 41 | } catch (error) { 42 | reactotron.log?.('signInAnonymously error', error) 43 | console.error(error) 44 | } finally { 45 | setLoading(false) 46 | } 47 | }, 48 | [setUser] 49 | ) 50 | 51 | return ( 52 |
53 | 54 | 55 | { 58 | return ( 59 | } 63 | size={5} 64 | ml="2" 65 | color="muted.400" 66 | /> 67 | } 68 | w="75%" 69 | maxW="300px" 70 | size={'md'} 71 | onChangeText={(value) => onChange(value.toLowerCase())} 72 | onBlur={onBlur} 73 | value={value} 74 | placeholder="Email" 75 | /> 76 | ) 77 | }} 78 | name="email" 79 | /> 80 | 81 | {errors.email?.message} 82 | 83 | 84 | 85 | 92 | 93 |
94 | ) 95 | } 96 | 97 | export function AuthStack() { 98 | return ( 99 | 104 | 105 | 106 | ) 107 | } 108 | -------------------------------------------------------------------------------- /libs/frontend/mobile-app/src/lib/features/movie/detail-screen/DetailScreen.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { createParam } from 'solito' 3 | 4 | import { Text, Box, VStack, Button, Spinner } from 'native-base' 5 | import { trpc } from '@conference-demos/trpc-client' 6 | import { InferQueryOutput } from '@conference-demos/api/trpc-server-edgedb' 7 | import { parseMovieId } from './parseMovieId' 8 | import { stringifyMovieId } from './stringifyMovieId' 9 | import { MoviePoster } from '../../home/MoviePoster' 10 | import { WatchlistMovieScreenNavigationProps } from '../../../navigation/Navigation' 11 | 12 | const { useParam } = createParam<{ id: number }>() 13 | 14 | type MovieById = InferQueryOutput<'tmdb.byId'> 15 | 16 | export function MovieDetailScreen({ 17 | navigation, 18 | }: WatchlistMovieScreenNavigationProps) { 19 | const [id] = useParam('id', { 20 | initial: 0, 21 | parse: parseMovieId, 22 | stringify: stringifyMovieId, 23 | }) 24 | 25 | const { mutate: addToWatchlist, isLoading: addToWatchlistLoading } = 26 | trpc.useMutation(['user.addToWatchlist']) 27 | const { mutate: removeFromWatchlist, isLoading: removeFromWatchlistLoading } = 28 | trpc.useMutation(['user.removeFromWatchlist']) 29 | 30 | const { data, isLoading: isWatchlistLoading } = trpc.useQuery([ 31 | 'user.watchlist', 32 | ]) 33 | const watchlist = data?.movies ?? [] 34 | const utils = trpc.useContext() 35 | const movieResult = trpc.useQuery(['tmdb.byId', { id }], { 36 | enabled: id > 0, 37 | }) 38 | 39 | const movie = movieResult.data 40 | 41 | let watchedMovieId = '' 42 | if (watchlist) { 43 | watchedMovieId = watchlist.find((m) => m.id === id)?.watchListId 44 | console.log('watchedMovieId', watchedMovieId) 45 | 46 | } 47 | 48 | React.useEffect(() => { 49 | navigation.setOptions({ title: movie?.title ?? 'Movie' }) 50 | }, [movie?.title, navigation]) 51 | 52 | if (!movieResult) return null 53 | 54 | function handleWatched(movie: MovieById) { 55 | if (!movie) { 56 | return 57 | } 58 | 59 | addToWatchlist( 60 | { id: movie.id.toString() }, 61 | { 62 | onSuccess() { 63 | utils.invalidateQueries(['user.watchlist']) 64 | }, 65 | } 66 | ) 67 | } 68 | 69 | function removeWatched(watchlistId: string) { 70 | removeFromWatchlist( 71 | { id: watchlistId }, 72 | { 73 | onSuccess() { 74 | utils.invalidateQueries(['user.watchlist']) 75 | }, 76 | } 77 | ) 78 | } 79 | 80 | if (movieResult.error) { 81 | return Error: {movieResult.error.message} 82 | } 83 | 84 | return movie ? ( 85 | 91 | 99 | 100 | {isWatchlistLoading ? ( 101 | 102 | ) : watchedMovieId ? ( 103 | 109 | ) : ( 110 | 116 | )} 117 | 118 | 119 | ) : null 120 | } 121 | -------------------------------------------------------------------------------- /libs/api/trpc-server-edgedb/src/lib/tmdb-router.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod' 2 | import { createRouter } from './context' 3 | import { MoviesSchema, MovieType } from './MoviesSchema' 4 | 5 | export const tmdbRouter = createRouter() 6 | .query('nowPlaying', { 7 | output: MoviesSchema, 8 | input: z.void(), 9 | async resolve({ ctx }) { 10 | const url = 'movie/now_playing' 11 | const response = await fetch( 12 | `https://api.themoviedb.org/3/${url}?page=1`, 13 | { 14 | headers: { 15 | Authorization: ctx.TMDB_TOKEN, 16 | }, 17 | } 18 | ) 19 | const responseJson = await response.json() 20 | 21 | const movies = responseJson.results ?? [] 22 | 23 | return { 24 | movies: movies.map((movie: MovieType) => ({ 25 | ...movie, 26 | posterImage: `https://image.tmdb.org/t/p/w500/${movie.poster_path}`, 27 | backdropImage: `https://image.tmdb.org/t/p/w500/${movie.backdrop_path}`, 28 | })), 29 | } 30 | }, 31 | }) 32 | .query('search', { 33 | output: MoviesSchema, 34 | input: z.object({ 35 | query: z.string(), 36 | }), 37 | async resolve({ ctx, input }) { 38 | const url = `search/movie?query=${input.query}` 39 | const response = await fetch(`https://api.themoviedb.org/3/${url}`, { 40 | headers: { 41 | Authorization: ctx.TMDB_TOKEN, 42 | }, 43 | }) 44 | 45 | const responseJson = await response.json() 46 | 47 | const movies = responseJson.results ?? [] 48 | 49 | return { 50 | movies: movies.map((movie: MovieType) => ({ 51 | ...movie, 52 | posterImage: `https://image.tmdb.org/t/p/w500/${movie.poster_path}`, 53 | backdropImage: `https://image.tmdb.org/t/p/w500/${movie.backdrop_path}`, 54 | })), 55 | } 56 | }, 57 | }) 58 | .query('byId', { 59 | // output: MovieSchema.nullable(), 60 | input: z.object({ 61 | id: z.number(), 62 | }), 63 | async resolve({ ctx, input }) { 64 | const url = `movie/${input.id}` 65 | const response = await fetch(`https://api.themoviedb.org/3/${url}`, { 66 | headers: { 67 | Authorization: ctx.TMDB_TOKEN, 68 | }, 69 | }) 70 | 71 | const responseJson = await response.json() 72 | 73 | const movie: MovieType = responseJson 74 | 75 | return { 76 | ...movie, 77 | posterImage: `https://image.tmdb.org/t/p/w500/${movie.poster_path}`, 78 | backdropImage: `https://image.tmdb.org/t/p/w500/${movie.backdrop_path}`, 79 | } 80 | }, 81 | }) 82 | .query('creditsForMovieId', { 83 | // output: MovieSchema.nullable(), 84 | input: z.object({ 85 | id: z.number(), 86 | }), 87 | async resolve({ ctx, input }) { 88 | const url = `movie/${input.id}/credits` 89 | const response = await fetch(`https://api.themoviedb.org/3/${url}`, { 90 | headers: { 91 | Authorization: ctx.TMDB_TOKEN, 92 | }, 93 | }) 94 | 95 | const responseJson = await response.json() 96 | 97 | const movie: MovieType = responseJson 98 | 99 | return movie 100 | }, 101 | }) 102 | .query('creditById', { 103 | // output: MovieSchema.nullable(), 104 | input: z.object({ 105 | id: z.string(), 106 | }), 107 | async resolve({ ctx, input }) { 108 | const url = `credit/${input.id}` 109 | const response = await fetch(`https://api.themoviedb.org/3/${url}`, { 110 | headers: { 111 | Authorization: ctx.TMDB_TOKEN, 112 | }, 113 | }) 114 | const responseJson = await response.json() 115 | 116 | const movie: MovieType = responseJson 117 | 118 | return movie 119 | }, 120 | }) 121 | .query('personById', { 122 | // output: MovieSchema.nullable(), 123 | input: z.object({ 124 | id: z.number(), 125 | }), 126 | async resolve({ ctx, input }) { 127 | const url = `person/${input.id}` 128 | const response = await fetch(`https://api.themoviedb.org/3/${url}`, { 129 | headers: { 130 | Authorization: ctx.TMDB_TOKEN, 131 | }, 132 | }) 133 | const responseJson = await response.json() 134 | 135 | const movie: MovieType = responseJson 136 | 137 | return movie 138 | }, 139 | }) 140 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tmdb-watchlist-edgedb", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "start": "nx serve", 7 | "build": "nx build", 8 | "test": "nx test", 9 | "vercel-build": "prisma generate && prisma migrate dev && nx build next-app", 10 | "edgedb": "edgedb", 11 | "edgedb:create-migration": "edgedb migration create", 12 | "edgedb:migrate": "edgedb migrate", 13 | "edgedb:codegen": "cd libs/database/edgedb-client && npx edgeql-js --output-dir src/lib/codegen --force-overwrite", 14 | "mobile:build:android:dev": "DEBUG=* cd apps/mobile && eas build --profile preview --platform android", 15 | "mobile:build:ios:dev": "DEBUG=* cd apps/mobile && rm -rf ios && eas build --profile preview --platform ios", 16 | "mobile:clean": "cd apps/mobile && expo prebuild --clean" 17 | }, 18 | "private": true, 19 | "dependencies": { 20 | "@expo/metro-config": "0.3.17", 21 | "@hookform/resolvers": "^2.9.6", 22 | "@react-native-async-storage/async-storage": "~1.17.3", 23 | "@react-navigation/bottom-tabs": "^6.3.1", 24 | "@react-navigation/native": "^6.0.10", 25 | "@react-navigation/native-stack": "^6.6.2", 26 | "@trpc/client": "^9.25.3", 27 | "@trpc/next": "^9.25.3", 28 | "@trpc/react": "^9.25.3", 29 | "@trpc/server": "^9.25.3", 30 | "core-js": "^3.6.5", 31 | "cors": "^2.8.5", 32 | "edgedb": "^0.21.1", 33 | "expo": "45.0.5", 34 | "expo-dev-client": "~1.0.0", 35 | "expo-linking": "^3.1.0", 36 | "expo-splash-screen": "~0.15.1", 37 | "expo-status-bar": "~1.3.0", 38 | "expo-structured-headers": "~2.2.1", 39 | "expo-updates": "~0.13.3", 40 | "expo-web-browser": "^10.2.1", 41 | "express": "4.17.2", 42 | "graphql": "^16.5.0", 43 | "native-base": "^3.4.7", 44 | "next": "^12.1.6", 45 | "node-fetch": "2", 46 | "react": "17.0.2", 47 | "react-dom": "17.0.2", 48 | "react-hook-form": "^7.33.1", 49 | "react-native": "0.68.2", 50 | "react-native-gesture-handler": "~2.2.1", 51 | "react-native-reanimated": "~2.8.0", 52 | "react-native-safe-area-context": "4.2.4", 53 | "react-native-screens": "~3.11.1", 54 | "react-native-svg": "12.3.0", 55 | "react-native-svg-transformer": "1.0.0", 56 | "react-native-web": "0.17.7", 57 | "react-query": "^3.39.1", 58 | "regenerator-runtime": "0.13.7", 59 | "solito": "^0.0.26", 60 | "superjson": "^1.9.1", 61 | "tslib": "^2.3.0", 62 | "zod": "^3.17.3" 63 | }, 64 | "devDependencies": { 65 | "@nrwl/cli": "14.3.6", 66 | "@nrwl/cypress": "14.3.6", 67 | "@nrwl/detox": "14.3.6", 68 | "@nrwl/eslint-plugin-nx": "14.3.6", 69 | "@nrwl/expo": "^14.3.1", 70 | "@nrwl/express": "^14.3.6", 71 | "@nrwl/jest": "14.3.6", 72 | "@nrwl/js": "14.3.6", 73 | "@nrwl/linter": "14.3.6", 74 | "@nrwl/next": "^14.3.6", 75 | "@nrwl/node": "14.3.6", 76 | "@nrwl/nx-cloud": "latest", 77 | "@nrwl/react": "14.3.6", 78 | "@nrwl/web": "14.3.6", 79 | "@nrwl/workspace": "14.3.6", 80 | "@svgr/webpack": "^5.5.0", 81 | "@testing-library/jest-dom": "5.16.4", 82 | "@testing-library/jest-native": "4.0.5", 83 | "@testing-library/react": "13.3.0", 84 | "@testing-library/react-native": "9.1.0", 85 | "@types/express": "4.17.13", 86 | "@types/jest": "27.4.1", 87 | "@types/node": "16.11.7", 88 | "@types/node-fetch": "^2.6.2", 89 | "@types/react": "18.0.13", 90 | "@types/react-dom": "18.0.5", 91 | "@types/react-native": "0.67.8", 92 | "@typescript-eslint/eslint-plugin": "~5.24.0", 93 | "@typescript-eslint/parser": "~5.24.0", 94 | "babel-jest": "27.5.1", 95 | "babel-preset-expo": "~9.1.0", 96 | "cypress": "^9.1.0", 97 | "detox": "19.7.1", 98 | "eslint": "~8.15.0", 99 | "eslint-config-next": "12.1.6", 100 | "eslint-config-prettier": "8.1.0", 101 | "eslint-plugin-cypress": "^2.10.3", 102 | "eslint-plugin-import": "2.26.0", 103 | "eslint-plugin-jsx-a11y": "6.5.1", 104 | "eslint-plugin-react": "7.30.0", 105 | "eslint-plugin-react-hooks": "4.6.0", 106 | "expo-cli": "5.4.9", 107 | "jest": "27.5.1", 108 | "jest-circus": "27.5.1", 109 | "jest-expo": "45.0.1", 110 | "metro-babel-register": "0.71.0", 111 | "metro-resolver": "0.71.0", 112 | "nx": "14.3.6", 113 | "prettier": "^2.6.2", 114 | "react-test-renderer": "18.2.0", 115 | "reactotron-react-native": "^5.0.2", 116 | "ts-jest": "27.1.4", 117 | "ts-node": "^10.8.2", 118 | "typescript": "~4.7.2" 119 | } 120 | } 121 | --------------------------------------------------------------------------------