Repository: braydenwerner/Wern-Fullstack-Template
Branch: master
Commit: 32d7b7111699
Files: 69
Total size: 48.3 KB
Directory structure:
gitextract_cbg5532v/
├── .prettierrc.js
├── LICENSE
├── README.md
├── client/
│ ├── .babelrc
│ ├── .eslintignore
│ ├── .eslintrc.js
│ ├── .gitignore
│ ├── codegen.yml
│ ├── next-env.d.ts
│ ├── package.json
│ ├── src/
│ │ ├── components/
│ │ │ ├── Components.txt
│ │ │ ├── elements/
│ │ │ │ ├── Nav/
│ │ │ │ │ └── Nav.tsx
│ │ │ │ ├── ThemeToggle/
│ │ │ │ │ ├── ThemeToggle.styled.ts
│ │ │ │ │ └── ThemeToggle.tsx
│ │ │ │ └── index.ts
│ │ │ └── modules/
│ │ │ └── index.ts
│ │ ├── config/
│ │ │ └── config.ts
│ │ ├── generated/
│ │ │ └── graphql.tsx
│ │ ├── graphql/
│ │ │ ├── mutations/
│ │ │ │ └── createUser.graphql
│ │ │ └── queries/
│ │ │ ├── getUser.graphql
│ │ │ └── getUsers.graphql
│ │ ├── hooks/
│ │ │ └── useMediaQuery.ts
│ │ ├── pages/
│ │ │ ├── 404.tsx
│ │ │ ├── _app.tsx
│ │ │ ├── _document.tsx
│ │ │ ├── api/
│ │ │ │ └── hello.ts
│ │ │ └── index.tsx
│ │ ├── providers/
│ │ │ └── AppProvider.tsx
│ │ ├── styles/
│ │ │ ├── constantStyles.ts
│ │ │ ├── global.ts
│ │ │ ├── index.ts
│ │ │ ├── styled.d.ts
│ │ │ └── theme.ts
│ │ └── util/
│ │ ├── createURQLClient.ts
│ │ └── isServer.ts
│ └── tsconfig.json
└── server/
├── .dockerignore
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── Dockerfile
├── dist/
│ ├── config.js
│ ├── entities/
│ │ ├── UserAccount.js
│ │ └── index.js
│ ├── index.js
│ ├── middleware/
│ │ └── isAuth.js
│ ├── migrations/
│ │ ├── 1622333914717-initDB.js
│ │ └── 1622348668437-MockUsers.js
│ ├── resolvers/
│ │ ├── UserAccount.js
│ │ ├── index.js
│ │ └── userInput.js
│ ├── types.js
│ └── utils/
│ └── validateRegister.js
├── ormconfig.json
├── package.json
├── src/
│ ├── config.ts
│ ├── entities/
│ │ ├── UserAccount.ts
│ │ └── index.ts
│ ├── env.d.ts
│ ├── index.ts
│ ├── middleware/
│ │ └── isAuth.ts
│ ├── migrations/
│ │ ├── 1622333914717-initDB.ts
│ │ └── 1622348668437-MockUsers.ts
│ ├── resolvers/
│ │ ├── UserAccount.ts
│ │ ├── index.ts
│ │ └── userInput.ts
│ ├── types.ts
│ └── utils/
│ └── validateRegister.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .prettierrc.js
================================================
module.exports = {
trailingComma: 'es5',
tabWidth: 2,
singleQuote: true,
arrowParens: 'always',
useTabs: false,
semi: false,
}
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Brayden Werner
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
### This repo is inspired by https://github.com/benawad/lireddit
## Features Include:
- Server-side rendered data from postgres
- Create user graphql mutation with password encryption
- Get users/user graphql query
- Light/dark theme support and switch component
## Uses the following technologies:
- React
- Next.js
- MaterialUI
- Styled-Components
- TypeGraphQL
- URQL
- ApolloServer(express)
- TypeORM
- PostgresSQL
- Node.js
- TypeScript
## Running Locally:
### Client:
```
npm install
npm run dev
```
### Server: (You must have a postgreSQL database running and update the DATABASE_URL in server/.env if you wish to add database functionality)
### A migration to create the users table can be found in server/src/migrations. To run it, uncomment the following line in server/src/index.ts
```
await conn.runMigrations()
```
```
npm install
npm run build
npm run dev2
```
## Folder Structure
```bash
├── client
│ └── src
│ ├── components
│ │ ├── elements
│ │ └── modules
│ ├── config
│ ├── generated
│ ├── graphql
│ │ ├── fragments
│ │ ├── mutations
│ │ └── queries
│ ├── hooks
│ ├── pages
│ │ └── api
│ ├── providers
│ ├── public
│ │ ├── fonts
│ │ └── images
│ ├── styles
│ └── util
└── server
├── dist
│ ├── entities
│ ├── middleware
│ ├── resolvers
│ └── utils
└── src
├── entities
├── middleware
├── migrations
├── resolvers
└── utils
```
## Hosting:
### Client: Hosted with vercel
### Server: Hosted with Heroku using PostgreSQL plugin
================================================
FILE: client/.babelrc
================================================
{
"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true }]]
}
================================================
FILE: client/.eslintignore
================================================
node_modules/
================================================
FILE: client/.eslintrc.js
================================================
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 12,
sourceType: 'module',
},
plugins: ['react', '@typescript-eslint'],
rules: {
'comma-dangle': 'never',
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-empty-interface': 'off',
},
}
================================================
FILE: client/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
================================================
FILE: client/codegen.yml
================================================
overwrite: true
schema: 'http://localhost:4000'
documents: 'src/graphql/**/*.graphql'
generates:
src/generated/graphql.tsx:
plugins:
- 'typescript'
- 'typescript-operations'
- 'typescript-urql'
================================================
FILE: client/next-env.d.ts
================================================
///
///
================================================
FILE: client/package.json
================================================
{
"name": "client",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"gen": "graphql-codegen --config codegen.yml"
},
"dependencies": {
"@material-ui/core": "^4.11.4",
"graphql": "^15.5.0",
"next": "10.2.2",
"next-urql": "^3.1.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-icons": "^4.2.0",
"react-switch": "^6.0.0",
"styled-components": "^5.3.0",
"urql": "^2.0.3"
},
"devDependencies": {
"@graphql-codegen/cli": "1.21.4",
"@graphql-codegen/typescript": "1.22.0",
"@graphql-codegen/typescript-operations": "^1.17.16",
"@graphql-codegen/typescript-urql": "^2.0.6",
"@types/styled-components": "^5.1.9",
"@typescript-eslint/eslint-plugin": "^4.24.0",
"@typescript-eslint/parser": "^4.24.0",
"babel-plugin-styled-components": "^1.12.0",
"eslint": "^7.27.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-import": "^2.23.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.3.1",
"eslint-plugin-react": "^7.23.2",
"typescript": "^4.2.4"
}
}
================================================
FILE: client/src/components/Components.txt
================================================
I like to break my components up into elements and modules.
An element is an invidual component that may be used in multiple places in the app. Ex: A button
A module is a larger component generally consists of several elements. Ex: A navbar containing button components
================================================
FILE: client/src/components/elements/Nav/Nav.tsx
================================================
import { AppBar, Toolbar, Typography } from '@material-ui/core'
export const Nav: React.FC = () => {
return (
Wern Fullstack Template
)
}
================================================
FILE: client/src/components/elements/ThemeToggle/ThemeToggle.styled.ts
================================================
import styled from 'styled-components'
export const SwitchContainer = styled.div`
position: fixed;
top: 1%;
right: 1%;
margin: 10px 0px 0px 0px;
z-index: 1;
`
================================================
FILE: client/src/components/elements/ThemeToggle/ThemeToggle.tsx
================================================
import { useContext } from 'react'
import { FaMoon, FaSun } from 'react-icons/fa'
import Switch from 'react-switch'
import { ThemeContext } from '../../../providers/AppProvider'
import { SwitchContainer } from './ThemeToggle.styled'
export const ThemeToggle: React.FC = () => {
const { toggleTheme, themeMode } = useContext(ThemeContext)
const handleThemeChange = () => {
toggleTheme()
}
return (
}
uncheckedIcon={
}
onChange={handleThemeChange}
/>
)
}
================================================
FILE: client/src/components/elements/index.ts
================================================
export { Nav } from './Nav/Nav'
export { ThemeToggle } from './ThemeToggle/ThemeToggle'
================================================
FILE: client/src/components/modules/index.ts
================================================
export {}
================================================
FILE: client/src/config/config.ts
================================================
const dev = process.env.NODE_ENV !== 'production'
export const URL = dev
? 'http://localhost:3000/api'
: 'https://fullstack-wern-boilerplate.vercel.app/api'
export const serverURL = dev
? 'http://localhost:4000'
: 'https://wern-fullstack-template.herokuapp.com/'
================================================
FILE: client/src/generated/graphql.tsx
================================================
import gql from 'graphql-tag';
import * as Urql from 'urql';
export type Maybe = T | null;
export type Exact = { [K in keyof T]: T[K] };
export type MakeOptional = Omit & { [SubKey in K]?: Maybe };
export type MakeMaybe = Omit & { [SubKey in K]: Maybe };
export type Omit = Pick>;
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: string;
String: string;
Boolean: boolean;
Int: number;
Float: number;
};
export type FieldError = {
__typename?: 'FieldError';
field: Scalars['String'];
message: Scalars['String'];
};
export type Mutation = {
__typename?: 'Mutation';
createUser: UserResponse;
};
export type MutationCreateUserArgs = {
options: UserInput;
};
export type Query = {
__typename?: 'Query';
user?: Maybe;
users?: Maybe>;
};
export type QueryUserArgs = {
id: Scalars['String'];
};
export type UserAccount = {
__typename?: 'UserAccount';
id: Scalars['Float'];
username: Scalars['String'];
email: Scalars['String'];
createdAt: Scalars['String'];
updatedAt: Scalars['String'];
};
export type UserInput = {
email: Scalars['String'];
username: Scalars['String'];
password: Scalars['String'];
};
export type UserResponse = {
__typename?: 'UserResponse';
errors?: Maybe>;
user?: Maybe;
};
export type CreateUserMutationVariables = Exact<{
username: Scalars['String'];
email: Scalars['String'];
password: Scalars['String'];
}>;
export type CreateUserMutation = (
{ __typename?: 'Mutation' }
& { createUser: (
{ __typename?: 'UserResponse' }
& { errors?: Maybe
)>>, user?: Maybe<(
{ __typename?: 'UserAccount' }
& Pick
)> }
) }
);
export type GetUserQueryVariables = Exact<{
id: Scalars['String'];
}>;
export type GetUserQuery = (
{ __typename?: 'Query' }
& { user?: Maybe<(
{ __typename?: 'UserAccount' }
& Pick
)> }
);
export type GetUsersQueryVariables = Exact<{ [key: string]: never; }>;
export type GetUsersQuery = (
{ __typename?: 'Query' }
& { users?: Maybe
)>> }
);
export const CreateUserDocument = gql`
mutation CreateUser($username: String!, $email: String!, $password: String!) {
createUser(options: {username: $username, email: $email, password: $password}) {
errors {
field
message
}
user {
id
username
}
}
}
`;
export function useCreateUserMutation() {
return Urql.useMutation(CreateUserDocument);
};
export const GetUserDocument = gql`
query getUser($id: String!) {
user(id: $id) {
id
username
email
}
}
`;
export function useGetUserQuery(options: Omit, 'query'> = {}) {
return Urql.useQuery({ query: GetUserDocument, ...options });
};
export const GetUsersDocument = gql`
query getUsers {
users {
id
username
email
}
}
`;
export function useGetUsersQuery(options: Omit, 'query'> = {}) {
return Urql.useQuery({ query: GetUsersDocument, ...options });
};
================================================
FILE: client/src/graphql/mutations/createUser.graphql
================================================
mutation CreateUser($username: String!, $email: String!, $password: String!) {
createUser(
options: { username: $username, email: $email, password: $password }
) {
errors {
field
message
}
user {
id
username
}
}
}
================================================
FILE: client/src/graphql/queries/getUser.graphql
================================================
query getUser($id: String!) {
user(id: $id) {
id
username
email
}
}
================================================
FILE: client/src/graphql/queries/getUsers.graphql
================================================
query getUsers {
users {
id
username
email
}
}
================================================
FILE: client/src/hooks/useMediaQuery.ts
================================================
import { useState, useEffect } from 'react'
export function useMediaQuery(query: string) {
const [matches, setMatches] = useState(false)
useEffect(() => {
const media = window.matchMedia(query)
if (media.matches !== matches) {
setMatches(media.matches)
}
const listener = () => setMatches(media.matches)
media.addEventListener('change', listener)
return () => media.removeEventListener('change', listener)
}, [matches, query])
return matches
}
================================================
FILE: client/src/pages/404.tsx
================================================
const Error: React.FC = () => {
return 404 Error
}
export default Error
================================================
FILE: client/src/pages/_app.tsx
================================================
import Head from 'next/head'
import type { AppProps } from 'next/app'
import { AppProvider } from '../providers/AppProvider'
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
Wern Template
>
)
}
export default MyApp
================================================
FILE: client/src/pages/_document.tsx
================================================
import React from 'react'
import Document, { Html, Head, Main, NextScript } from 'next/document'
import { ServerStyleSheets } from '@material-ui/core/styles'
export default class MyDocument extends Document {
render() {
return (
)
}
}
MyDocument.getInitialProps = async (ctx) => {
const sheets = new ServerStyleSheets()
const originalRenderPage = ctx.renderPage
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(),
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: [
...React.Children.toArray(initialProps.styles),
sheets.getStyleElement(),
],
}
}
================================================
FILE: client/src/pages/api/hello.ts
================================================
export default (req: any, res: any) => {
console.log(req)
res.status(200).json({ name: 'John Doe' })
}
================================================
FILE: client/src/pages/index.tsx
================================================
import { withUrqlClient } from 'next-urql'
import styled from 'styled-components'
import { /*useGetUserQuery*/ useGetUsersQuery } from '../generated/graphql'
import { Nav, ThemeToggle } from '../components/elements/index'
import { createUrqlClient } from '../util/createURQLClient'
import {
CenterContainer,
StyledGridContainer,
StyledHeaderText,
StyledSubText,
} from '../styles/constantStyles'
import { useMediaQuery } from '../hooks/useMediaQuery'
const Home: React.FC = () => {
const [{ data }] = useGetUsersQuery()
// example query with id parameter
// const [user] = useGetUserQuery({ variables: { id: '1' } })
const largerThan500px = useMediaQuery('(min-width: 775px)')
return (
<>
{data &&
data.users?.map((user, i: number) => {
return (
{user.username}
Id: {user.id}
{user.email}
)
})}
>
)
}
interface StyledUserContainerProps {
largerThan500px: boolean
}
const StyledUserContainer = styled.div`
width: ${(props) => (props.largerThan500px ? '700px' : '90%')};
height: 100%;
padding: 15px;
margin: 20px 0px 20px 0px;
border-radius: 10px;
background-color: ${(props) => props.theme.secondary};
`
// creates client with server side rendering enabled
export default withUrqlClient(createUrqlClient, { ssr: true })(Home)
================================================
FILE: client/src/providers/AppProvider.tsx
================================================
import React, { useEffect, createContext, useState, useMemo } from 'react'
import { ThemeProvider } from 'styled-components'
import { GlobalStyles, theme } from '../styles/index'
export const ThemeContext = createContext({
themeMode: 'dark',
toggleTheme: () => {
return
},
})
export const AppProvider: React.FC = ({ children }) => {
const [themeMode, setThemeMode] = useState('dark')
const currentTheme = (theme as any)[themeMode]
useEffect(() => {
setThemeMode(localStorage.getItem('theme') || 'dark')
}, [])
useEffect(() => {
localStorage.setItem('theme', themeMode)
}, [themeMode])
const toggleTheme = () => {
setThemeMode((oldTheme) => {
if (oldTheme === 'light') return 'dark'
else return 'light'
})
}
// combine into one object for global ThemeContext state
const value = useMemo(() => ({ themeMode, toggleTheme }), [themeMode])
return (
{children}
)
}
================================================
FILE: client/src/styles/constantStyles.ts
================================================
import styled from 'styled-components'
import { Grid } from '@material-ui/core'
export const CenterContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: 100%;
`
export const StyledGridContainer = styled(Grid)`
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
`
export const StyledHeaderText = styled.div`
font-size: 20px;
`
export const StyledSubText = styled.div`
font-size: 15px;
color: ${(props) => props.theme.subText};
`
================================================
FILE: client/src/styles/global.ts
================================================
import { createGlobalStyle, css } from 'styled-components'
export default createGlobalStyle`
${({ theme }) => css`
html {
height: 100%;
body {
display: flex;
flex-direction: column;
height: 100%;
margin: 0;
background: ${theme.background};
color: ${theme.primaryText};
font-family: Newsreader;
}
}
`}
`
================================================
FILE: client/src/styles/index.ts
================================================
export { default as GlobalStyles } from './global'
export { default as theme } from './theme'
================================================
FILE: client/src/styles/styled.d.ts
================================================
import {} from 'styled-components'
import { ThemeType } from './theme'
declare module 'styled-components' {
export interface DefaultTheme extends ThemeType {}
}
================================================
FILE: client/src/styles/theme.ts
================================================
export type ThemeType = typeof theme['light']
const theme = {
light: {
background: '#ACDDDE',
secondary: '#CAF1DE',
secondaryDark: '#E1F8DC',
primaryText: 'black',
subText: 'gray',
},
dark: {
background: '#444444',
secondary: '#1E2328',
secondaryDark: '#878683',
primaryText: 'white',
subText: 'gray',
},
}
export const commonColors = {
red: '#C6493A',
}
export default theme
================================================
FILE: client/src/util/createURQLClient.ts
================================================
import { dedupExchange, cacheExchange, fetchExchange } from '@urql/core'
import { serverURL } from '../config/config'
// import { isServer } from './isServer'
export const createUrqlClient = (ssrExchange: any, ctx: any) => {
// uncomment if necessary to send cookie to the server. Useful for authentification
// let cookie = ''
// if (isServer()) {
// cookie = ctx?.req?.headers?.cookie
// }
return {
url: serverURL,
fetchOptions: {
credentials: 'include' as const,
// this will send a cookie to the server
// headers: cookie
// ? {
// cookie,
// }
// : undefined,
},
exchanges: [
dedupExchange,
cacheExchange,
ssrExchange, // Add `ssr` in front of the `fetchExchange`
fetchExchange,
],
}
}
================================================
FILE: client/src/util/isServer.ts
================================================
export const isServer = () => typeof window === 'undefined'
================================================
FILE: client/tsconfig.json
================================================
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
================================================
FILE: server/.dockerignore
================================================
node_modules
npm-debug.log
================================================
FILE: server/.eslintignore
================================================
node_modules
================================================
FILE: server/.eslintrc.js
================================================
module.exports = {
env: {
browser: true,
commonjs: true,
es2021: true,
},
extends: ['standard'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 12,
},
plugins: ['@typescript-eslint'],
rules: {
'comma-dangle': 'never',
},
}
================================================
FILE: server/.gitignore
================================================
# add .env for real application
node_modules/
================================================
FILE: server/Dockerfile
================================================
FROM node:14
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
COPY .env.production .env
ENV NODE_ENV production
EXPOSE 8080
CMD [ "node", "dist/index.js" ]
================================================
FILE: server/dist/config.js
================================================
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__prod__ = void 0;
exports.__prod__ = process.env.NODE_ENV === 'production';
//# sourceMappingURL=config.js.map
================================================
FILE: server/dist/entities/UserAccount.js
================================================
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UserAccount = void 0;
const type_graphql_1 = require("type-graphql");
const typeorm_1 = require("typeorm");
let UserAccount = class UserAccount extends typeorm_1.BaseEntity {
};
__decorate([
type_graphql_1.Field(),
typeorm_1.PrimaryGeneratedColumn(),
__metadata("design:type", Number)
], UserAccount.prototype, "id", void 0);
__decorate([
type_graphql_1.Field(),
typeorm_1.Column({ unique: true }),
__metadata("design:type", String)
], UserAccount.prototype, "username", void 0);
__decorate([
type_graphql_1.Field(),
typeorm_1.Column({ unique: true }),
__metadata("design:type", String)
], UserAccount.prototype, "email", void 0);
__decorate([
typeorm_1.Column(),
__metadata("design:type", String)
], UserAccount.prototype, "password", void 0);
__decorate([
type_graphql_1.Field(() => String),
typeorm_1.CreateDateColumn(),
__metadata("design:type", Date)
], UserAccount.prototype, "createdAt", void 0);
__decorate([
type_graphql_1.Field(() => String),
typeorm_1.UpdateDateColumn(),
__metadata("design:type", Date)
], UserAccount.prototype, "updatedAt", void 0);
UserAccount = __decorate([
type_graphql_1.ObjectType(),
typeorm_1.Entity()
], UserAccount);
exports.UserAccount = UserAccount;
//# sourceMappingURL=UserAccount.js.map
================================================
FILE: server/dist/entities/index.js
================================================
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var UserAccount_1 = require("./UserAccount");
Object.defineProperty(exports, "UserAccount", { enumerable: true, get: function () { return UserAccount_1.UserAccount; } });
//# sourceMappingURL=index.js.map
================================================
FILE: server/dist/index.js
================================================
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
require("reflect-metadata");
require("dotenv-safe/config");
const express_1 = __importDefault(require("express"));
const path_1 = __importDefault(require("path"));
const cors_1 = __importDefault(require("cors"));
const apollo_server_express_1 = require("apollo-server-express");
const type_graphql_1 = require("type-graphql");
const typeorm_1 = require("typeorm");
const index_1 = require("./entities/index");
const index_2 = require("./resolvers/index");
const main = () => __awaiter(void 0, void 0, void 0, function* () {
console.log(process.env.DATABASE_URL);
yield typeorm_1.createConnection({
type: 'postgres',
url: process.env.DATABASE_URL,
logging: true,
synchronize: true,
entities: [index_1.UserAccount],
migrations: [path_1.default.join(__dirname, './migrations/*')],
ssl: {
rejectUnauthorized: false,
},
});
const app = express_1.default();
app.set('trust proxy', 1);
app.use(cors_1.default({
origin: process.env.CORS_ORIGIN,
credentials: true,
}));
const apolloServer = new apollo_server_express_1.ApolloServer({
schema: yield type_graphql_1.buildSchema({
resolvers: [index_2.UserResolver],
validate: false,
}),
context: ({ req, res }) => ({
req,
res,
}),
});
apolloServer.applyMiddleware({
app,
cors: false,
path: '/',
});
app.listen(parseInt(process.env.PORT), () => {
console.log(`server started on localhost:${process.env.PORT}`);
});
});
main().catch((err) => {
console.error(err);
});
//# sourceMappingURL=index.js.map
================================================
FILE: server/dist/middleware/isAuth.js
================================================
//# sourceMappingURL=isAuth.js.map
================================================
FILE: server/dist/migrations/1622333914717-initDB.js
================================================
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.initDB1622333914717 = void 0;
class initDB1622333914717 {
constructor() {
this.name = 'initDB1622333914717';
}
up(queryRunner) {
return __awaiter(this, void 0, void 0, function* () {
yield queryRunner.query(`CREATE TABLE "User_Account" ("id" SERIAL NOT NULL, "username" character varying NOT NULL, "email" character varying NOT NULL, "password" character varying NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_78a916df40e02a9deb1c4b75edb" UNIQUE ("username"), CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"), CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`);
});
}
down(queryRunner) {
return __awaiter(this, void 0, void 0, function* () {
yield queryRunner.query(`DROP TABLE "user_account"`);
});
}
}
exports.initDB1622333914717 = initDB1622333914717;
//# sourceMappingURL=1622333914717-initDB.js.map
================================================
FILE: server/dist/migrations/1622348668437-MockUsers.js
================================================
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MockUsers1622348668437 = void 0;
class MockUsers1622348668437 {
up(queryRunner) {
return __awaiter(this, void 0, void 0, function* () {
yield queryRunner.query(`
insert into UserAccount (id, username, email, password, "createdAt", "updatedAt") values (1, 'sbauldry0', 'ctrewett0@unicef.org', 'jhZk91W5W', '4/18/2021', '7/22/2020');
insert into UserAccount (id, username, email, password, "createdAt", "updatedAt") values (2, 'gduiguid1', 'asallnow1@dell.com', 'U58IqmAs', '3/2/2021', '8/7/2020');
insert into UserAccount (id, username, email, password, "createdAt", "updatedAt") values (3, 'dwrey2', 'sportress2@google.com.au', 'MjoXnUBNnE', '1/23/2021', '10/5/2020');
insert into UserAccount (id, username, email, password, "createdAt", "updatedAt") values (4, 'hwebley3', 'whauxby3@live.com', 'aclMGjnKB4jC', '12/2/2020', '5/7/2021');
insert into UserAccount (id, username, email, password, "createdAt", "updatedAt") values (5, 'lscourgie4', 'kkerwick4@pen.io', '6slgst6', '6/28/2020', '3/13/2021');
insert into UserAccount (id, username, email, password, "createdAt", "updatedAt") values (6, 'hbrundall5', 'teaglestone5@tmall.com', 'hgmvqTD', '11/10/2020', '11/26/2020');
`);
});
}
down(_) {
return __awaiter(this, void 0, void 0, function* () { });
}
}
exports.MockUsers1622348668437 = MockUsers1622348668437;
//# sourceMappingURL=1622348668437-MockUsers.js.map
================================================
FILE: server/dist/resolvers/UserAccount.js
================================================
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UserResolver = void 0;
const type_graphql_1 = require("type-graphql");
const typeorm_1 = require("typeorm");
const argon2 = __importStar(require("argon2"));
const validateRegister_1 = require("../utils/validateRegister");
const index_1 = require("../entities/index");
const userInput_1 = require("./userInput");
let FieldError = class FieldError {
};
__decorate([
type_graphql_1.Field(),
__metadata("design:type", String)
], FieldError.prototype, "field", void 0);
__decorate([
type_graphql_1.Field(),
__metadata("design:type", String)
], FieldError.prototype, "message", void 0);
FieldError = __decorate([
type_graphql_1.ObjectType()
], FieldError);
let UserResponse = class UserResponse {
};
__decorate([
type_graphql_1.Field(() => [FieldError], { nullable: true }),
__metadata("design:type", Array)
], UserResponse.prototype, "errors", void 0);
__decorate([
type_graphql_1.Field(() => index_1.UserAccount, { nullable: true }),
__metadata("design:type", index_1.UserAccount)
], UserResponse.prototype, "user", void 0);
UserResponse = __decorate([
type_graphql_1.ObjectType()
], UserResponse);
let UserResolver = class UserResolver {
user(id) {
const user = index_1.UserAccount.findOne({ where: { id } });
if (!user)
return null;
return user;
}
users() {
console.log('users query reached');
const users = index_1.UserAccount.find();
if (!users)
return null;
return users;
}
createUser(options) {
return __awaiter(this, void 0, void 0, function* () {
const errors = validateRegister_1.validateRegister(options);
if (errors) {
return { errors };
}
const hashedPassword = yield argon2.hash(options.password);
let user;
try {
const result = yield typeorm_1.getConnection()
.createQueryBuilder()
.insert()
.into(index_1.UserAccount)
.values({
username: options.username,
email: options.email,
password: hashedPassword,
})
.returning('*')
.execute();
user = result.raw[0];
}
catch (err) {
if (err.code === '23505') {
return {
errors: [
{
field: 'username',
message: 'username already taken',
},
],
};
}
}
return { user };
});
}
};
__decorate([
type_graphql_1.Query(() => index_1.UserAccount, { nullable: true }),
__param(0, type_graphql_1.Arg('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", void 0)
], UserResolver.prototype, "user", null);
__decorate([
type_graphql_1.Query(() => [index_1.UserAccount], { nullable: true }),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], UserResolver.prototype, "users", null);
__decorate([
type_graphql_1.Mutation(() => UserResponse),
__param(0, type_graphql_1.Arg('options')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [userInput_1.UserInput]),
__metadata("design:returntype", Promise)
], UserResolver.prototype, "createUser", null);
UserResolver = __decorate([
type_graphql_1.Resolver(index_1.UserAccount)
], UserResolver);
exports.UserResolver = UserResolver;
//# sourceMappingURL=UserAccount.js.map
================================================
FILE: server/dist/resolvers/index.js
================================================
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var UserAccount_1 = require("./UserAccount");
Object.defineProperty(exports, "UserResolver", { enumerable: true, get: function () { return UserAccount_1.UserResolver; } });
//# sourceMappingURL=index.js.map
================================================
FILE: server/dist/resolvers/userInput.js
================================================
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UserInput = void 0;
const type_graphql_1 = require("type-graphql");
let UserInput = class UserInput {
};
__decorate([
type_graphql_1.Field(),
__metadata("design:type", String)
], UserInput.prototype, "email", void 0);
__decorate([
type_graphql_1.Field(),
__metadata("design:type", String)
], UserInput.prototype, "username", void 0);
__decorate([
type_graphql_1.Field(),
__metadata("design:type", String)
], UserInput.prototype, "password", void 0);
UserInput = __decorate([
type_graphql_1.InputType()
], UserInput);
exports.UserInput = UserInput;
//# sourceMappingURL=userInput.js.map
================================================
FILE: server/dist/types.js
================================================
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=types.js.map
================================================
FILE: server/dist/utils/validateRegister.js
================================================
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateRegister = void 0;
exports.validateRegister = (options) => {
if (!options.email.includes('@')) {
return [
{
field: 'email',
message: 'invalid email',
},
];
}
if (options.username.length <= 2) {
return [
{
field: 'username',
message: 'length must be greater than 2',
},
];
}
if (options.username.includes('@')) {
return [
{
field: 'username',
message: 'cannot include an @',
},
];
}
if (options.password.length <= 2) {
return [
{
field: 'password',
message: 'length must be greater than 2',
},
];
}
return null;
};
//# sourceMappingURL=validateRegister.js.map
================================================
FILE: server/ormconfig.json
================================================
{
"type": "postgres",
"host": "localhost",
"port": "5432",
"username": "postgres",
"password": "postgres",
"database": "thumbnailgame",
"entities": ["dist/entities/*.js"]
}
================================================
FILE: server/package.json
================================================
{
"name": "wern-fullstack-template-server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"gen-env": "gen-env-types .env -o src/env.d.ts -e .",
"build": "tsc",
"watch": "tsc -w",
"dev": "nodemon dist/index.js",
"start": "node dist/index.js",
"start2": "ts-node src/index.ts",
"dev2": "nodemon --exec ts-node src/index.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/connect-redis": "^0.0.14",
"@types/cors": "^2.8.7",
"@types/express": "^4.17.7",
"@types/express-session": "^1.17.0",
"@types/ioredis": "^4.17.3",
"@types/node": "^14.0.27",
"@types/nodemailer": "^6.4.0",
"@types/redis": "^2.8.25",
"@types/uuid": "^8.0.1",
"gen-env-types": "^1.0.3",
"nodemon": "^2.0.4",
"ts-node": "^8.10.2",
"typescript": "^3.9.7"
},
"dependencies": {
"apollo-server-express": "^2.16.1",
"argon2": "^0.26.2",
"connect-redis": "^5.0.0",
"cors": "^2.8.5",
"dataloader": "^2.0.0",
"dotenv-safe": "^8.2.0",
"express": "^4.17.1",
"express-session": "^1.17.1",
"graphql": "^15.3.0",
"nodemailer": "^6.4.11",
"pg": "^8.3.0",
"reflect-metadata": "^0.1.13",
"type-graphql": "^1.0.0-rc.3",
"typeorm": "^0.2.25",
"uuid": "^8.3.0"
},
"mikro-orm": {
"useTsNode": true,
"configPaths": [
"./src/mikro-orm.config.ts",
"./dist/mikro-orm.config.js"
]
}
}
================================================
FILE: server/src/config.ts
================================================
export const __prod__ = process.env.NODE_ENV === 'production'
================================================
FILE: server/src/entities/UserAccount.ts
================================================
import { ObjectType, Field } from 'type-graphql'
import {
Entity,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
Column,
BaseEntity,
} from 'typeorm'
@ObjectType()
@Entity()
export class UserAccount extends BaseEntity {
@Field()
@PrimaryGeneratedColumn()
id!: number
@Field()
@Column({ unique: true })
username!: string
@Field()
@Column({ unique: true })
email!: string
@Column()
password!: string
@Field(() => String)
@CreateDateColumn()
createdAt: Date
@Field(() => String)
@UpdateDateColumn()
updatedAt: Date
}
================================================
FILE: server/src/entities/index.ts
================================================
export { UserAccount } from './UserAccount'
================================================
FILE: server/src/env.d.ts
================================================
declare namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string;
REDIS_URL: string;
PORT: string;
SESSION_SECRET: string;
CORS_ORIGIN: string;
}
}
================================================
FILE: server/src/index.ts
================================================
import 'reflect-metadata'
import 'dotenv-safe/config'
import express from 'express'
import path from 'path'
import cors from 'cors'
import { ApolloServer } from 'apollo-server-express'
import { buildSchema } from 'type-graphql'
import { createConnection } from 'typeorm'
import { __prod__ } from './config'
import { UserAccount } from './entities/index'
import { UserResolver } from './resolvers/index'
const main = async () => {
console.log(process.env.DATABASE_URL)
/*const conn =*/ await createConnection({
type: 'postgres',
url: process.env.DATABASE_URL,
logging: true,
// do not want synchronize true in production, possiblility of losing data
synchronize: false,
entities: [UserAccount],
migrations: [path.join(__dirname, './migrations/*')],
// need this to use postgres heroku plugin
ssl: {
rejectUnauthorized: false,
},
})
// await conn.runMigrations()
const app = express()
app.set('trust proxy', 1)
app.use(
cors({
origin: process.env.CORS_ORIGIN,
credentials: true,
})
)
const apolloServer = new ApolloServer({
schema: await buildSchema({
resolvers: [UserResolver],
validate: false,
}),
context: ({ req, res }) => ({
req,
res,
}),
})
apolloServer.applyMiddleware({
app,
cors: false,
path: '/',
})
app.listen(parseInt(process.env.PORT!), () => {
console.log(`server started on localhost:${process.env.PORT!}`)
})
}
main().catch((err) => {
console.error(err)
})
================================================
FILE: server/src/middleware/isAuth.ts
================================================
// import { MiddlewareFn } from 'type-graphql'
// import { MyContext } from '../types'
// export const isAuth: MiddlewareFn = ({ context }, next) => {
// if (!context.req.session.userId) {
// throw new Error('not authenticated')
// }
// return next()
// }
================================================
FILE: server/src/migrations/1622333914717-initDB.ts
================================================
import { MigrationInterface, QueryRunner } from 'typeorm'
export class initDB1622333914717 implements MigrationInterface {
name = 'initDB1622333914717'
public async up(queryRunner: QueryRunner): Promise {
await queryRunner.query(
`CREATE TABLE "User_Account" ("id" SERIAL NOT NULL, "username" character varying NOT NULL, "email" character varying NOT NULL, "password" character varying NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_78a916df40e02a9deb1c4b75edb" UNIQUE ("username"), CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"), CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`
)
}
public async down(queryRunner: QueryRunner): Promise {
await queryRunner.query(`DROP TABLE "user_account"`)
}
}
================================================
FILE: server/src/migrations/1622348668437-MockUsers.ts
================================================
import { MigrationInterface, QueryRunner } from 'typeorm'
export class MockUsers1622348668437 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise {
await queryRunner.query(`
insert into User_Account (id, username, email, password, "createdAt", "updatedAt") values (2, 'sbauldry0', 'ctrewett0@unicef.org', 'jhZk91W5W', '4/18/2021', '7/22/2020');
insert into User_Account (id, username, email, password, "createdAt", "updatedAt") values (3, 'gduiguid1', 'asallnow1@dell.com', 'U58IqmAs', '3/2/2021', '8/7/2020');
insert into User_Account (id, username, email, password, "createdAt", "updatedAt") values (4, 'dwrey2', 'sportress2@google.com.au', 'MjoXnUBNnE', '1/23/2021', '10/5/2020');
insert into User_Account (id, username, email, password, "createdAt", "updatedAt") values (5, 'hwebley3', 'whauxby3@live.com', 'aclMGjnKB4jC', '12/2/2020', '5/7/2021');
insert into User_Account (id, username, email, password, "createdAt", "updatedAt") values (6, 'lscourgie4', 'kkerwick4@pen.io', '6slgst6', '6/28/2020', '3/13/2021');
insert into User_Account (id, username, email, password, "createdAt", "updatedAt") values (7, 'hbrundall5', 'teaglestone5@tmall.com', 'hgmvqTD', '11/10/2020', '11/26/2020');
`)
}
public async down(_: QueryRunner): Promise {}
}
================================================
FILE: server/src/resolvers/UserAccount.ts
================================================
import {
Resolver,
Mutation,
Arg,
Field,
// Ctx,
ObjectType,
Query,
// FieldResolver,
// Root,
} from 'type-graphql'
import { getConnection } from 'typeorm'
import * as argon2 from 'argon2'
// import { MyContext } from '../types'
import { validateRegister } from '../utils/validateRegister'
import { UserAccount } from '../entities/index'
import { UserInput } from './userInput'
@ObjectType()
class FieldError {
@Field()
field: string
@Field()
message: string
}
@ObjectType()
// return the errors associated and the user
class UserResponse {
@Field(() => [FieldError], { nullable: true })
errors?: FieldError[]
@Field(() => UserAccount, { nullable: true })
user?: UserAccount
}
@Resolver(UserAccount)
export class UserResolver {
@Query(() => UserAccount, { nullable: true })
user(@Arg('id') id: string) {
const user = UserAccount.findOne({ where: { id } })
if (!user) return null
return user
}
@Query(() => [UserAccount], { nullable: true })
users() {
console.log('users query reached')
const users = UserAccount.find()
if (!users) return null
return users
}
@Mutation(() => UserResponse)
async createUser(@Arg('options') options: UserInput): Promise {
const errors = validateRegister(options)
if (errors) {
return { errors }
}
const hashedPassword = await argon2.hash(options.password)
let user
try {
const result = await getConnection()
.createQueryBuilder()
.insert()
.into(UserAccount)
.values({
username: options.username,
email: options.email,
password: hashedPassword,
})
.returning('*')
.execute()
user = result.raw[0]
} catch (err) {
if (err.code === '23505') {
return {
errors: [
{
field: 'username',
message: 'username already taken',
},
],
}
}
}
return { user }
}
}
================================================
FILE: server/src/resolvers/index.ts
================================================
export { UserResolver } from './UserAccount'
================================================
FILE: server/src/resolvers/userInput.ts
================================================
import { InputType, Field } from 'type-graphql'
@InputType()
export class UserInput {
@Field()
email: string
@Field()
username: string
@Field()
password: string
}
================================================
FILE: server/src/types.ts
================================================
import { Request, Response } from 'express'
export type MyContext = {
req: Request
res: Response
}
================================================
FILE: server/src/utils/validateRegister.ts
================================================
import { UserInput } from '../resolvers/userInput'
export const validateRegister = (options: UserInput) => {
if (!options.email.includes('@')) {
return [
{
field: 'email',
message: 'invalid email',
},
]
}
if (options.username.length <= 2) {
return [
{
field: 'username',
message: 'length must be greater than 2',
},
]
}
if (options.username.includes('@')) {
return [
{
field: 'username',
message: 'cannot include an @',
},
]
}
if (options.password.length <= 2) {
return [
{
field: 'password',
message: 'length must be greater than 2',
},
]
}
return null
}
================================================
FILE: server/tsconfig.json
================================================
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"lib": ["dom", "es6", "es2017", "esnext.asynciterable"],
"skipLibCheck": true,
"sourceMap": true,
"outDir": "./dist",
"moduleResolution": "node",
"removeComments": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"resolveJsonModule": true,
"baseUrl": "."
},
"exclude": ["node_modules"],
"include": ["./src/**/*.tsx", "./src/**/*.ts", "1622348668437-MockUsers.ts"]
}