This commit is contained in:
2020-10-10 13:52:57 +03:00
committed by Stepan Usatiuk
commit 442e882b72
24 changed files with 18262 additions and 0 deletions

45
.eslintrc.json Normal file
View File

@@ -0,0 +1,45 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint",
"prettier",
"import"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"prettier/@typescript-eslint",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:import/typescript"
],
"env": {
"node": true
},
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module",
"project": "./tsconfig.json"
},
"settings": {
"import/parsers": {
"@typescript-eslint/parser": [
".ts",
".tsx"
]
},
"import/resolver": {
"typescript": {
"alwaysTryTypes": true,
"project": [
"./tsconfig.json"
]
}
}
},
"rules": {
"@typescript-eslint/require-await": "warn"
}
}

10
.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
.idea/
node_modules/
build/
tmp/
temp/
dist/
ormconfig.json
.env
.directory
.history

5
.prettierrc Normal file
View File

@@ -0,0 +1,5 @@
{
"trailingComma": "all",
"tabWidth": 4,
"endOfLine": "auto"
}

8
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"cSpell.words": [
"typeorm"
],
"files.exclude": {
"frontend": true
}
}

38
frontend/.eslintrc.json Normal file
View File

@@ -0,0 +1,38 @@
{
"plugins": [
"jest",
"react",
"react-hooks",
"html"
],
"extends": [
"plugin:jest/recommended",
"plugin:react-hooks/recommended",
"plugin:react/recommended"
],
"env": {
"browser": true
},
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"project": "./tsconfig.json"
},
"settings": {
"import/parsers": {
"@typescript-eslint/parser": [
".ts",
".tsx"
]
},
"import/resolver": {
"typescript": {
"alwaysTryTypes": true,
"project": [
"./tsconfig.json"
]
}
}
}
}

13
frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
.idea/
node_modules/
build/
tmp/
temp/
dist/
ormconfig.json
ormconfig.test.json
.env
.cache
.directory
.history
.parcel-cache

13278
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

42
frontend/package.json Normal file
View File

@@ -0,0 +1,42 @@
{
"name": "photos-frontend",
"scripts": {
"start": "parcel serve src/index.html",
"build": "parcel build src/index.html",
"lint": "eslint ./src/** --ext .js,.jsx,.ts,.tsx",
"test": "jest"
},
"postcss": {
"plugins": {
"autoprefixer": true
}
},
"dependencies": {
"@typescript-eslint/eslint-plugin": "^4.4.0",
"@typescript-eslint/parser": "^4.4.0",
"eslint": "^7.10.0",
"eslint-config-prettier": "^6.12.0",
"eslint-import-resolver-typescript": "^2.3.0",
"eslint-plugin-html": "^6.1.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jest": "^24.1.0",
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.21.3",
"eslint-plugin-react-hooks": "^4.1.2",
"jest": "^26.5.2",
"parcel": "^2.0.0-nightly.419",
"prettier": "^2.1.2",
"prettier-eslint": "^11.0.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"typescript": "^4.0.3"
},
"devDependencies": {
"@types/eslint": "^7.2.4",
"@types/eslint-plugin-prettier": "^3.1.0",
"@types/prettier": "^2.1.1",
"@types/react": "^16.9.51",
"@types/react-dom": "^16.9.8"
}
}

14
frontend/src/index.html Normal file
View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>My Parcel Project</title>
</head>
<body>
<div id="root"></div>
<script src="./index.tsx"></script>
</body>
</html>

4
frontend/src/index.tsx Normal file
View File

@@ -0,0 +1,4 @@
import * as React from "react";
import { render } from "react-dom";
render(<h1>Hello World</h1>, document.getElementById("root"));

24
frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"lib": [
"es2017",
"dom"
],
"jsx": "react",
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./dist",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"noImplicitAny": true,
"allowSyntheticDefaultImports": true,
"strictFunctionTypes": true,
"strictNullChecks": true
},
"include": [
"./src/**/*.ts",
"./src/**/*.tsx"
],
}

25
ormconfig.ci.json Normal file
View File

@@ -0,0 +1,25 @@
{
"type": "mariadb",
"host": "localhost",
"port": 3306,
"username": "photos",
"password": "photos",
"database": "photos_test",
"synchronize": true,
"logging": false,
"entities": [
"src/entity/**/*.ts"
],
"migrations": [
"src/migration/**/*.ts"
],
"subscribers": [
"src/subscriber/**/*.ts"
],
"cli": {
"entitiesDir": "src/entity",
"migrationsDir": "src/migration",
"subscribersDir": "src/subscriber"
},
"charset": "utf8mb4"
}

25
ormconfig.dev.json Normal file
View File

@@ -0,0 +1,25 @@
{
"type": "mariadb",
"host": "db",
"port": 3306,
"username": "photos",
"password": "photos",
"database": "photos",
"synchronize": true,
"logging": false,
"entities": [
"src/entity/**/*.ts"
],
"migrations": [
"src/migration/**/*.ts"
],
"subscribers": [
"src/subscriber/**/*.ts"
],
"cli": {
"entitiesDir": "src/entity",
"migrationsDir": "src/migration",
"subscribersDir": "src/subscriber"
},
"charset": "utf8mb4"
}

25
ormconfig.example.json Normal file
View File

@@ -0,0 +1,25 @@
{
"type": "mariadb",
"host": "localhost",
"port": 3306,
"username": "photos",
"password": "photos",
"database": "photos",
"synchronize": true,
"logging": false,
"entities": [
"src/entity/**/*.ts"
],
"migrations": [
"src/migration/**/*.ts"
],
"subscribers": [
"src/subscriber/**/*.ts"
],
"cli": {
"entitiesDir": "src/entity",
"migrationsDir": "src/migration",
"subscribersDir": "src/subscriber"
},
"charset": "utf8mb4"
}

25
ormconfig.test.json Normal file
View File

@@ -0,0 +1,25 @@
{
"type": "mariadb",
"host": "dbtest",
"port": 3306,
"username": "photos",
"password": "photos",
"database": "photos_test",
"synchronize": true,
"logging": false,
"entities": [
"src/entity/**/*.ts"
],
"migrations": [
"src/migration/**/*.ts"
],
"subscribers": [
"src/subscriber/**/*.ts"
],
"cli": {
"entitiesDir": "src/entity",
"migrationsDir": "src/migration",
"subscribersDir": "src/subscriber"
},
"charset": "utf8mb4"
}

4447
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

43
package.json Normal file
View File

@@ -0,0 +1,43 @@
{
"name": "photos",
"version": "0.0.1",
"scripts": {
"start-frontend": "cd frontend && npm start",
"ts-node-dev": "ts-node-dev ./src/server.ts",
"dev": "cross-env NODE_ENV=development concurrently npm:ts-node-dev npm:start-frontend -c 'blue,green'",
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint ./src/** --ext .js,.jsx,.ts,.tsx",
"lint-frontend": "cd frontend && npm run lint",
"lint-all": "npm run lint && npm run lint-frontend"
},
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "^4.4.0",
"@typescript-eslint/parser": "^4.4.0",
"bcrypt": "^5.0.0",
"concurrently": "^5.3.0",
"cross-env": "^7.0.2",
"eslint": "^7.10.0",
"eslint-config-prettier": "^6.12.0",
"eslint-import-resolver-typescript": "^2.3.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-prettier": "^3.1.4",
"jsonwebtoken": "^8.5.1",
"koa": "^2.13.0",
"prettier": "^2.1.2",
"prettier-eslint": "^11.0.0",
"ts-node": "^9.0.0",
"ts-node-dev": "^1.0.0-pre.63",
"typeorm": "^0.2.28",
"typescript": "^4.0.3"
},
"devDependencies": {
"@types/bcrypt": "^3.0.0",
"@types/concurrently": "^5.2.1",
"@types/eslint": "^7.2.3",
"@types/eslint-plugin-prettier": "^3.1.0",
"@types/jsonwebtoken": "^8.5.0",
"@types/koa": "^2.11.4",
"@types/prettier": "^2.1.1"
}
}

11
photos.code-workspace Normal file
View File

@@ -0,0 +1,11 @@
{
"folders": [
{
"path": "."
},
{
"path": "frontend"
}
],
"settings": {}
}

7
src/app.ts Normal file
View File

@@ -0,0 +1,7 @@
import * as Koa from "koa";
export const app = new Koa();
app.use(async (ctx) => {
ctx.body = "hello!";
});

8
src/config/database.ts Normal file
View File

@@ -0,0 +1,8 @@
import "../entity/User";
import { Connection, createConnection } from "typeorm";
import { config } from "./";
export async function connect(): Promise<Connection> {
return createConnection(config.dbConnectionOptions);
}

65
src/config/index.ts Normal file
View File

@@ -0,0 +1,65 @@
import * as fs from "fs";
import { ConnectionOptions } from "typeorm";
import { sys } from "typescript";
export enum EnvType {
production,
development,
test,
}
export interface IConfig {
env: EnvType;
port: number;
jwtSecret: string;
dbConnectionOptions: ConnectionOptions | null;
}
const production: IConfig = {
env: EnvType.production,
port: process.env.PORT ? parseInt(process.env.PORT, 10) : 3000,
jwtSecret: ((): string => {
if (process.env.JWT_SECRET === undefined) {
console.log("JWT_SECRET is not set");
process.exit(1);
}
return process.env.JWT_SECRET;
})(),
dbConnectionOptions: null,
};
const development: IConfig = {
...production,
env: EnvType.development,
jwtSecret: "DEVSECRET",
dbConnectionOptions:
process.env.NODE_ENV === "development"
? fs.existsSync("./ormconfig.dev.json")
? (JSON.parse(
fs.readFileSync("./ormconfig.dev.json").toString(),
) as ConnectionOptions)
: null
: null,
};
const test: IConfig = {
...production,
env: EnvType.test,
jwtSecret: "TESTSECRET",
dbConnectionOptions:
process.env.NODE_ENV === "test"
? process.env.CI
? (JSON.parse(
fs.readFileSync("./ormconfig.ci.json").toString(),
) as ConnectionOptions)
: (JSON.parse(
fs.readFileSync("./ormconfig.test.json").toString(),
) as ConnectionOptions)
: null,
};
const envs: { [key: string]: IConfig } = { production, development, test };
const env = process.env.NODE_ENV || "production";
const currentConfig = envs[env];
export { currentConfig as config };

66
src/entity/User.ts Normal file
View File

@@ -0,0 +1,66 @@
import * as bcrypt from "bcrypt";
import * as jwt from "jsonwebtoken";
import {
BaseEntity,
Column,
Entity,
Index,
PrimaryGeneratedColumn,
} from "typeorm";
import { config } from "../config";
export type IUserJSON = Pick<User, "id" | "username">;
export interface IUserJWT extends IUserJSON {
ext: number;
iat: number;
}
export interface IUserAuthJSON extends IUserJSON {
jwt: string;
}
@Entity()
export class User extends BaseEntity {
@PrimaryGeneratedColumn()
public id: number;
@Column({ length: 190 })
@Index({ unique: true })
public username: string;
@Column({ length: 190 })
@Index({ unique: true })
public email: string;
@Column({ length: 190 })
public passwordHash: string;
constructor(username: string, email: string) {
super();
this.username = username;
this.email = email;
}
public async verifyPassword(password: string): Promise<boolean> {
return bcrypt.compare(password, this.passwordHash);
}
public async setPassword(password: string): Promise<void> {
this.passwordHash = await bcrypt.hash(password, 10);
}
public toJSON(): IUserJSON {
const { id, username } = this;
return { id, username };
}
public toAuthJSON(): IUserAuthJSON {
const { id, username } = this;
return { id, username, jwt: this.toJWT() };
}
public toJWT(): string {
return jwt.sign(this.toJSON(), config.jwtSecret, { expiresIn: "31d" });
}
}

11
src/server.ts Normal file
View File

@@ -0,0 +1,11 @@
import { app } from "./app";
import { config } from "./config";
import { connect } from "./config/database";
connect()
.then((connection) => {
console.log(`Connected to ${connection.name}`);
app.listen(config.port);
console.log(`Listening at ${config.port}`);
})
.catch((e) => console.log(e));

23
tsconfig.json Normal file
View File

@@ -0,0 +1,23 @@
{
"compilerOptions": {
"lib": [
"es2017"
],
"target": "es2017",
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./dist",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"noImplicitAny": true,
"strictFunctionTypes": true,
"strictNullChecks": true
},
"include": [
"./src/**/*.ts",
],
"exclude": [
"frontend"
]
}