separate client and server apps

This commit is contained in:
Joseph Ditton 2021-11-16 19:14:46 -07:00
parent e5f6840013
commit cba40b6aff
32 changed files with 3910 additions and 178 deletions

View File

@ -14,11 +14,12 @@ module.exports = {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
ignorePatterns: ['.eslintrc.js', '.babelrc'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-function': 'off'
},
};

5
.gitignore vendored
View File

@ -1,6 +1,7 @@
# compiled output
/dist
/node_modules
dist
static
node_modules
# Logs
logs

View File

@ -1,12 +1,49 @@
# Nest Starter App
## Description
A starter app with LTI1p0 enabled for NestJS
A starter app with Postgres, NestJS, and React
## Prerequisites
### asdf-vm
Tool versions are managed using `asdf-vm`. You will need to have `asdf-vm` installed first.
## Setup
### Tool versions
Install the tool versions by running
```bash
$ asdf install
```
### Install yarn
We will use `yarn` instead of `npm` for package managment
```bash
$ npm install -g yarn
```
### .env
Create a file in the root called `.env` and copy the contents of `.env.example`
## SSL
### Dependencies
To install the dependencies run
```bash
$ yarn # this is same thing as `yarn install`
```
### Database
Create the database
```bash
$ pc_ctl start # this starts postgres
$ createdb neststarterappdevelopement # creates a postgres database
```
Run the migrations
```bash
yarn db:migrate
```
Migrations need to be run again everytime a new migration is created
### SSL
Create a ssl key and certificate an place them in the root directory
```bash
@ -14,48 +51,28 @@ $ openssl req -x509 -newkey rsa:4096 -keyout private-key.pem -out public-cert.pe
```
Where this key will only be used for development you can leave all of the information blank.
## Installation
```bash
$ npm install
```
## Running the app
```bash
# development
$ npm run start
$ yarn start
# watch mode
$ npm run start:dev
$ yarn start:dev
# production mode
$ npm run start:prod
$ yarn start:prod
```
## Test
```bash
# unit tests
$ npm run test
$ yarn test
# e2e tests
$ npm run test:e2e
$ yarn test:e2e
# test coverage
$ npm run test:cov
$ yarn test:cov
```
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](LICENSE).

39
client/app.jsx Normal file
View File

@ -0,0 +1,39 @@
import { useState } from 'react';
import { setConstantValue } from 'typescript';
const App = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const submit = () => {
fetch('/sign_in', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
});
};
return (
<>
<div>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<button type="button" onClick={submit}>Login</button>
</>
);
};
export default App;

5
client/index.js Normal file
View File

@ -0,0 +1,5 @@
import ReactDOM from 'react-dom';
import App from './app';
const app = document.getElementById('app');
ReactDOM.render(<App />, app);

13
client/package.json Normal file
View File

@ -0,0 +1,13 @@
{
"name": "nestclientstarterapp-client",
"version": "1.0.0",
"description": "",
"scripts": {},
"author": "",
"license": "ISC",
"devDependencies": {},
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}

45
client/yarn.lock Normal file
View File

@ -0,0 +1,45 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
loose-envify@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
react-dom@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler "^0.20.2"
react@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler@^0.20.2:
version "0.20.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"

View File

@ -1,4 +1,4 @@
{
"collection": "@nestjs/schematics",
"sourceRoot": "src"
"sourceRoot": "server"
}

View File

@ -6,28 +6,40 @@
"private": true,
"license": "UNLICENSED",
"scripts": {
"start:db": "pg_ctl status || pg_ctl start",
"stop:db": "pg_ctl status && pg_ctl stop",
"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js --config src/database/cli_config.ts",
"db:start": "pg_ctl status || pg_ctl start",
"db:stop": "pg_ctl status && pg_ctl stop",
"db:migration:create": "cd server/migrations && typeorm migration:create -n ",
"db:migrate": "yarn db:start && yarn typeorm migration:run",
"db:migrate:undo": "yarn db:start && yarn typeorm migration:revert",
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"build": "nest build && yarn client:build",
"format": "prettier --write \"server/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "yarn start:db && nest start --watch",
"start:debug": "yarn start:db && nest start --debug --watch",
"start:dev": "yarn db:start && nest start --watch",
"start:debug": "yarn db:start && nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"lint": "eslint \"{server,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
"test:e2e": "jest --config ./test/jest-e2e.json",
"client:build": "parcel build ./client/index.js --dist-dir static",
"client:watch": "parcel watch ./client/index.js --dist-dir static"
},
"dependencies": {
"@nestjs/common": "^8.0.0",
"@nestjs/core": "^8.0.0",
"@nestjs/platform-express": "^8.0.0",
"@nestjs/serve-static": "^2.2.2",
"@nestjs/typeorm": "^8.0.2",
"bcrypt": "^5.0.1",
"cookie-parser": "^1.4.6",
"dotenv": "^10.0.0",
"hbs": "^4.1.2",
"jsonwebtoken": "^8.5.1",
"morgan": "^1.10.0",
"pg": "^8.7.1",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
@ -38,6 +50,7 @@
"@nestjs/cli": "^8.0.0",
"@nestjs/schematics": "^8.0.0",
"@nestjs/testing": "^8.0.0",
"@types/cookie-parser": "^1.4.2",
"@types/express": "^4.17.13",
"@types/jest": "^27.0.1",
"@types/node": "^16.0.0",
@ -48,10 +61,10 @@
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"jest": "^27.0.6",
"prettier": "^2.3.2",
"parcel": "^2.0.1",
"prettier": "^2.4.1",
"supertest": "^6.1.3",
"ts-jest": "^27.0.3",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "^3.10.1",
"typescript": "^4.3.5"
@ -62,7 +75,7 @@
"json",
"ts"
],
"rootDir": "src",
"rootDir": "server",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"

8
server/app.controller.ts Normal file
View File

@ -0,0 +1,8 @@
import { Controller, Get, Render } from '@nestjs/common';
@Controller()
export class AppController {
@Get()
@Render('index')
index() {}
}

13
server/app.module.ts Normal file
View File

@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { config } from './database/config';
import { UsersModule } from './modules/users.module';
@Module({
imports: [TypeOrmModule.forRoot(config), UsersModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

View File

@ -0,0 +1,27 @@
import { Body, Controller, Post, Res } from '@nestjs/common';
import { UsersService } from 'server/providers/services/users.service';
import { SignInDto } from '../dto/sign_in.dto';
import { Response } from 'express';
// this is kind of a misnomer because we are doing token based auth
// instead of session based auth
@Controller()
export class SessionsController {
constructor(private usersService: UsersService) {}
@Post('/sign_in')
async signIn(@Body() body: SignInDto, @Res() res: Response) {
console.log("DO I GET RAN?")
const verified = await this.usersService.verify(
body.username,
body.password,
);
if (!verified) {
res.status(400);
console.log("here too??")
res.json({ message: 'Invalid email or password' });
return;
}
res.json({ success: true });
}
}

View File

@ -0,0 +1,22 @@
import '../env';
export = process.env.NODE_ENV === 'development'
? {
type: 'postgres',
host: 'localhost',
port: 5432,
database: process.env.DATABASE_URL,
autoLoadEntities: true,
migrations: ['src/database/migrations/*.ts'],
cli: {
migrationsDir: 'src/database/migrations',
},
}
: {
url: process.env.DATABASE_URL,
ssl: { rejectUnauthorized: true },
migrations: ['src/database/migrations/*.ts'],
cli: {
migrationsDir: 'src/database/migrations',
},
};

16
server/database/config.ts Normal file
View File

@ -0,0 +1,16 @@
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import '../env';
export const config: TypeOrmModuleOptions =
process.env.NODE_ENV === 'development'
? {
type: 'postgres',
host: 'localhost',
port: 5432,
database: process.env.DATABASE_URL,
autoLoadEntities: true,
}
: {
url: process.env.DATABASE_URL,
ssl: { rejectUnauthorized: true },
};

View File

@ -0,0 +1,37 @@
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
export class AddUser1637028716848 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: 'user',
columns: [
{
name: 'id',
type: 'int',
isPrimary: true,
},
{
name: 'name',
type: 'text',
isNullable: false,
},
{
name: 'password_hash',
type: 'text',
isNullable: false,
},
{
name: 'email',
type: 'text',
isNullable: false,
},
],
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('user');
}
}

View File

@ -0,0 +1,4 @@
export class SignInDto {
username: string;
password: string;
}

View File

@ -0,0 +1,16 @@
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
email: string;
@Column()
name: string;
@Column()
password_hash: string;
}

2
server/env.ts Normal file
View File

@ -0,0 +1,2 @@
import { config } from 'dotenv';
config();

26
server/main.ts Normal file
View File

@ -0,0 +1,26 @@
import './env';
import * as fs from 'fs';
import { NestFactory } from '@nestjs/core';
import { join } from 'path';
import * as morgan from 'morgan';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
async function bootstrap() {
let httpsOptions;
if (process.env.NODE_ENV === 'development') {
httpsOptions = {
key: fs.readFileSync('./private-key.pem'),
cert: fs.readFileSync('./public-cert.pem'),
};
}
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
httpsOptions,
});
app.use(morgan('tiny'));
app.useStaticAssets(join(__dirname, '..', 'static'));
app.setBaseViewsDir(join(__dirname, '../', 'views'));
app.setViewEngine('hbs');
await app.listen(process.env.PORT);
}
bootstrap();

View File

@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from 'server/entities/user.entity';
import { SessionsController } from '../controllers/sessions.controller';
import { UsersService } from '../providers/services/users.service';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [SessionsController],
providers: [UsersService],
})
export class UsersModule {}

View File

@ -0,0 +1,27 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import bcrypt from 'bcrypt';
import { User } from '../../entities/user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRespository: Repository<User>,
) {}
findBy(options: Record<string, any>) {
return this.usersRespository.findOne(options);
}
find(id: number) {
return this.usersRespository.findOne(id);
}
async verify(email: string, password: string) {
const user = await this.usersRespository.findOne({ email });
if (!user) return false;
return bcrypt.compare(password, user.password_hash);
}
}

View File

@ -1,12 +0,0 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}

View File

@ -1,22 +0,0 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot(process.env.NODE_ENV === 'development' ? {
type: 'postgres',
host: 'localhost',
port: 5432,
database: process.env.DATABASE_URL,
autoLoadEntities: true,
} : {
url: process.env.DATABASE_URL,
ssl: { rejectUnauthorized: true }
})
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

View File

@ -1,21 +0,0 @@
import { config } from 'dotenv';
import * as fs from 'fs';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
config();
async function bootstrap() {
let httpsOptions;
if (process.env.NODE_ENV === 'development') {
httpsOptions = {
key: fs.readFileSync('./private-key.pem'),
cert: fs.readFileSync('./public-cert.pem'),
}
}
const app = await NestFactory.create(AppModule, {
httpsOptions
});
await app.listen(process.env.PORT);
}
bootstrap();

9
views/index.hbs Normal file
View File

@ -0,0 +1,9 @@
<html>
<head>
</head>
<body>
<div id="app" />
<script src="index.js"></script>
</body>
</html>

3595
yarn.lock

File diff suppressed because it is too large Load Diff