Add backend app with basic Docker support

This commit is contained in:
Norbi Peti 2021-11-09 21:52:42 +01:00
parent 49480c5d67
commit 0e684a69be
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
33 changed files with 5773 additions and 1 deletions

5
backend/.dockerignore Normal file
View file

@ -0,0 +1,5 @@
node_modules
npm-debug.log
/dist
# Cache used by TypeScript's incremental build
*.tsbuildinfo

4
backend/.eslintignore Normal file
View file

@ -0,0 +1,4 @@
node_modules/
dist/
coverage/
.eslintrc.js

3
backend/.eslintrc.js Normal file
View file

@ -0,0 +1,3 @@
module.exports = {
extends: '@loopback/eslint-config',
};

64
backend/.gitignore vendored Normal file
View file

@ -0,0 +1,64 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# Transpiled JavaScript files from Typescript
/dist
# Cache used by TypeScript's incremental build
*.tsbuildinfo

5
backend/.mocharc.json Normal file
View file

@ -0,0 +1,5 @@
{
"exit": true,
"recursive": true,
"require": "source-map-support/register"
}

2
backend/.prettierignore Normal file
View file

@ -0,0 +1,2 @@
dist
*.json

7
backend/.prettierrc Normal file
View file

@ -0,0 +1,7 @@
{
"bracketSpacing": false,
"singleQuote": true,
"printWidth": 80,
"trailingComma": "all",
"arrowParens": "avoid"
}

6
backend/.yo-rc.json Normal file
View file

@ -0,0 +1,6 @@
{
"@loopback/cli": {
"packageManager": "npm",
"version": "2.22.1"
}
}

36
backend/DEVELOPING.md Normal file
View file

@ -0,0 +1,36 @@
# Developer's Guide
We use Visual Studio Code for developing LoopBack and recommend the same to our
users.
## VSCode setup
Install the following extensions:
- [eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
- [prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
## Development workflow
### Visual Studio Code
1. Start the build task (Cmd+Shift+B) to run TypeScript compiler in the
background, watching and recompiling files as you change them. Compilation
errors will be shown in the VSCode's "PROBLEMS" window.
2. Execute "Run Rest Task" from the Command Palette (Cmd+Shift+P) to re-run the
test suite and lint the code for both programming and style errors. Linting
errors will be shown in VSCode's "PROBLEMS" window. Failed tests are printed
to terminal output only.
### Other editors/IDEs
1. Open a new terminal window/tab and start the continuous build process via
`npm run build:watch`. It will run TypeScript compiler in watch mode,
recompiling files as you change them. Any compilation errors will be printed
to the terminal.
2. In your main terminal window/tab, run `npm run test:dev` to re-run the test
suite and lint the code for both programming and style errors. You should run
this command manually whenever you have new changes to test. Test failures
and linter errors will be printed to the terminal.

28
backend/Dockerfile Normal file
View file

@ -0,0 +1,28 @@
# Check out https://hub.docker.com/_/node to select a new base image
FROM node:16-slim
# Set to a non-root built-in user `node`
USER node
# Create app directory (with user `node`)
RUN mkdir -p /home/node/app
WORKDIR /home/node/app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY --chown=node package*.json ./
RUN npm install
# Bundle app source code
COPY --chown=node . .
RUN npm run build
# Bind to all network interfaces so that it can be mapped to the host OS
ENV HOST=0.0.0.0 PORT=3000
EXPOSE ${PORT}
CMD [ "node", "." ]

75
backend/README.md Normal file
View file

@ -0,0 +1,75 @@
# szakdolgozat-backend
This application is generated using [LoopBack 4 CLI](https://loopback.io/doc/en/lb4/Command-line-interface.html) with the
[initial project layout](https://loopback.io/doc/en/lb4/Loopback-application-layout.html).
## Install dependencies
By default, dependencies were installed when this application was generated.
Whenever dependencies in `package.json` are changed, run the following command:
```sh
npm install
```
To only install resolved dependencies in `package-lock.json`:
```sh
npm ci
```
## Run the application
```sh
npm start
```
You can also run `node .` to skip the build step.
Open http://127.0.0.1:3000 in your browser.
## Rebuild the project
To incrementally build the project:
```sh
npm run build
```
To force a full build by cleaning up cached artifacts:
```sh
npm run rebuild
```
## Fix code style and formatting issues
```sh
npm run lint
```
To automatically fix such issues:
```sh
npm run lint:fix
```
## Other useful commands
- `npm run migrate`: Migrate database schemas for models
- `npm run openapi-spec`: Generate OpenAPI spec into a file
- `npm run docker:build`: Build a Docker image for this application
- `npm run docker:run`: Run this application inside a Docker container
## Tests
```sh
npm test
```
## What's next
Please check out [LoopBack 4 documentation](https://loopback.io/doc/en/lb4/) to
understand how you can continue to add features to this application.
[![LoopBack](https://github.com/loopbackio/loopback-next/raw/master/docs/site/imgs/branding/Powered-by-LoopBack-Badge-(blue)-@2x.png)](http://loopback.io/)

5075
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

69
backend/package.json Normal file
View file

@ -0,0 +1,69 @@
{
"name": "szakdolgozat-backend",
"version": "0.0.1",
"description": "Szakdolgozat backend",
"keywords": [
"loopback-application",
"loopback"
],
"main": "dist/index.js",
"types": "dist/index.d.ts",
"engines": {
"node": ">=10.16"
},
"scripts": {
"build": "lb-tsc",
"build:watch": "lb-tsc --watch",
"lint": "npm run eslint && npm run prettier:check",
"lint:fix": "npm run eslint:fix && npm run prettier:fix",
"prettier:cli": "lb-prettier \"**/*.ts\" \"**/*.js\"",
"prettier:check": "npm run prettier:cli -- -l",
"prettier:fix": "npm run prettier:cli -- --write",
"eslint": "lb-eslint --report-unused-disable-directives .",
"eslint:fix": "npm run eslint -- --fix",
"pretest": "npm run rebuild",
"test": "lb-mocha --allow-console-logs \"dist/__tests__\"",
"posttest": "npm run lint",
"test:dev": "lb-mocha --allow-console-logs dist/__tests__/**/*.js && npm run posttest",
"docker:build": "docker build -t szakdolgozat-backend .",
"docker:run": "docker run -p 3000:3000 -d szakdolgozat-backend",
"premigrate": "npm run build",
"migrate": "node ./dist/migrate",
"preopenapi-spec": "npm run build",
"openapi-spec": "node ./dist/openapi-spec",
"prestart": "npm run rebuild",
"start": "node -r source-map-support/register .",
"clean": "lb-clean dist *.tsbuildinfo .eslintcache",
"rebuild": "npm run clean && npm run build"
},
"repository": {
"type": "git",
"url": ""
},
"author": "NorbiPeti <szatmari.norbert.peter@gmail.com>",
"license": "",
"files": [
"README.md",
"dist",
"src",
"!*/__tests__"
],
"dependencies": {
"@loopback/boot": "^3.4.4",
"@loopback/core": "^2.18.0",
"@loopback/repository": "^3.7.3",
"@loopback/rest": "^10.1.0",
"@loopback/rest-explorer": "^3.3.4",
"@loopback/service-proxy": "^3.2.4",
"tslib": "^2.0.0"
},
"devDependencies": {
"@loopback/build": "^7.0.2",
"source-map-support": "^0.5.20",
"@loopback/testlab": "^3.4.4",
"@types/node": "^10.17.60",
"@loopback/eslint-config": "^11.0.2",
"eslint": "^7.32.0",
"typescript": "~4.4.4"
}
}

88
backend/public/index.html Normal file
View file

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Szakdolgozat backend</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/x-icon" href="https://loopback.io/favicon.ico">
<style>
h3 {
margin-left: 25px;
text-align: center;
}
a, a:visited {
color: #3f5dff;
}
h3 a {
margin-left: 10px;
}
a:hover, a:focus, a:active {
color: #001956;
}
.power {
position: absolute;
bottom: 25px;
left: 50%;
transform: translateX(-50%);
}
.info {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%)
}
.info h1 {
text-align: center;
margin-bottom: 0;
}
.info p {
text-align: center;
margin-bottom: 3em;
margin-top: 1em;
}
@media (prefers-color-scheme: dark) {
body {
background-color: rgb(29, 30, 32);
color: white;
}
a, a:visited {
color: #4990e2;
}
a:hover, a:focus, a:active {
color: #2b78ff;
}
}
</style>
</head>
<body>
<div class="info">
<h1>szakdolgozat-backend</h1>
<p>Version 1.0.0</p>
<h3>OpenAPI spec: <a href="./openapi.json">/openapi.json</a></h3>
<h3>API Explorer: <a href="./explorer">/explorer</a></h3>
</div>
<footer class="power">
<a href="https://loopback.io" target="_blank">
<img src="https://loopback.io/images/branding/powered-by-loopback/blue/powered-by-loopback-sm.png" />
</a>
</footer>
</body>
</html>

View file

@ -0,0 +1,3 @@
# Tests
Please place your tests in this folder.

View file

@ -0,0 +1,31 @@
import {Client} from '@loopback/testlab';
import {SzakdolgozatBackendApplication} from '../..';
import {setupApplication} from './test-helper';
describe('HomePage', () => {
let app: SzakdolgozatBackendApplication;
let client: Client;
before('setupApplication', async () => {
({app, client} = await setupApplication());
});
after(async () => {
await app.stop();
});
it('exposes a default home page', async () => {
await client
.get('/')
.expect(200)
.expect('Content-Type', /text\/html/);
});
it('exposes self-hosted explorer', async () => {
await client
.get('/explorer/')
.expect(200)
.expect('Content-Type', /text\/html/)
.expect(/<title>LoopBack API Explorer/);
});
});

View file

@ -0,0 +1,21 @@
import {Client, expect} from '@loopback/testlab';
import {SzakdolgozatBackendApplication} from '../..';
import {setupApplication} from './test-helper';
describe('PingController', () => {
let app: SzakdolgozatBackendApplication;
let client: Client;
before('setupApplication', async () => {
({app, client} = await setupApplication());
});
after(async () => {
await app.stop();
});
it('invokes GET /ping', async () => {
const res = await client.get('/ping?msg=world').expect(200);
expect(res.body).to.containEql({greeting: 'Hello from LoopBack'});
});
});

View file

@ -0,0 +1,32 @@
import {SzakdolgozatBackendApplication} from '../..';
import {
createRestAppClient,
givenHttpServerConfig,
Client,
} from '@loopback/testlab';
export async function setupApplication(): Promise<AppWithClient> {
const restConfig = givenHttpServerConfig({
// Customize the server configuration here.
// Empty values (undefined, '') will be ignored by the helper.
//
// host: process.env.HOST,
// port: +process.env.PORT,
});
const app = new SzakdolgozatBackendApplication({
rest: restConfig,
});
await app.boot();
await app.start();
const client = createRestAppClient(app);
return {app, client};
}
export interface AppWithClient {
app: SzakdolgozatBackendApplication;
client: Client;
}

View file

@ -0,0 +1,44 @@
import {BootMixin} from '@loopback/boot';
import {ApplicationConfig} from '@loopback/core';
import {
RestExplorerBindings,
RestExplorerComponent,
} from '@loopback/rest-explorer';
import {RepositoryMixin} from '@loopback/repository';
import {RestApplication} from '@loopback/rest';
import {ServiceMixin} from '@loopback/service-proxy';
import path from 'path';
import {MySequence} from './sequence';
export {ApplicationConfig};
export class SzakdolgozatBackendApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
constructor(options: ApplicationConfig = {}) {
super(options);
// Set up the custom sequence
this.sequence(MySequence);
// Set up default home page
this.static('/', path.join(__dirname, '../public'));
// Customize @loopback/rest-explorer configuration here
this.configure(RestExplorerBindings.COMPONENT).to({
path: '/explorer',
});
this.component(RestExplorerComponent);
this.projectRoot = __dirname;
// Customize @loopback/boot Booter Conventions here
this.bootOptions = {
controllers: {
// Customize ControllerBooter Conventions here
dirs: ['controllers'],
extensions: ['.controller.js'],
nested: true,
},
};
}
}

View file

@ -0,0 +1,9 @@
# Controllers
This directory contains source files for the controllers exported by this app.
To add a new empty controller, type in `lb4 controller [<name>]` from the
command-line of your application's root directory.
For more information, please visit
[Controller generator](http://loopback.io/doc/en/lb4/Controller-generator.html).

View file

@ -0,0 +1 @@
export * from './ping.controller';

View file

@ -0,0 +1,55 @@
import {inject} from '@loopback/core';
import {
Request,
RestBindings,
get,
response,
ResponseObject,
} from '@loopback/rest';
/**
* OpenAPI response for ping()
*/
const PING_RESPONSE: ResponseObject = {
description: 'Ping Response',
content: {
'application/json': {
schema: {
type: 'object',
title: 'PingResponse',
properties: {
greeting: {type: 'string'},
date: {type: 'string'},
url: {type: 'string'},
headers: {
type: 'object',
properties: {
'Content-Type': {type: 'string'},
},
additionalProperties: true,
},
},
},
},
},
};
/**
* A simple controller to bounce back http requests
*/
export class PingController {
constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {}
// Map to `GET /ping`
@get('/ping')
@response(200, PING_RESPONSE)
ping(): object {
// Reply with a greeting, the current time, the url, and request headers
return {
greeting: 'Hello from LoopBack',
date: new Date(),
url: this.req.url,
headers: Object.assign({}, this.req.headers),
};
}
}

View file

@ -0,0 +1,3 @@
# Datasources
This directory contains config for datasources used by this app.

39
backend/src/index.ts Normal file
View file

@ -0,0 +1,39 @@
import {ApplicationConfig, SzakdolgozatBackendApplication} from './application';
export * from './application';
export async function main(options: ApplicationConfig = {}) {
const app = new SzakdolgozatBackendApplication(options);
await app.boot();
await app.start();
const url = app.restServer.url;
console.log(`Server is running at ${url}`);
console.log(`Try ${url}/ping`);
return app;
}
if (require.main === module) {
// Run the application
const config = {
rest: {
port: +(process.env.PORT ?? 3000),
host: process.env.HOST,
// The `gracePeriodForClose` provides a graceful close for http/https
// servers with keep-alive clients. The default value is `Infinity`
// (don't force-close). If you want to immediately destroy all sockets
// upon stop, set its value to `0`.
// See https://www.npmjs.com/package/stoppable
gracePeriodForClose: 5000, // 5 seconds
openApiSpec: {
// useful when used with OpenAPI-to-GraphQL to locate your application
setServersFromRequest: true,
},
},
};
main(config).catch(err => {
console.error('Cannot start the application.', err);
process.exit(1);
});
}

20
backend/src/migrate.ts Normal file
View file

@ -0,0 +1,20 @@
import {SzakdolgozatBackendApplication} from './application';
export async function migrate(args: string[]) {
const existingSchema = args.includes('--rebuild') ? 'drop' : 'alter';
console.log('Migrating schemas (%s existing schema)', existingSchema);
const app = new SzakdolgozatBackendApplication();
await app.boot();
await app.migrateSchema({existingSchema});
// Connectors usually keep a pool of opened connections,
// this keeps the process running even after all work is done.
// We need to exit explicitly.
process.exit(0);
}
migrate(process.argv).catch(err => {
console.error('Cannot migrate database schema', err);
process.exit(1);
});

View file

@ -0,0 +1,3 @@
# Models
This directory contains code for models provided by this app.

View file

@ -0,0 +1,23 @@
import {ApplicationConfig} from '@loopback/core';
import {SzakdolgozatBackendApplication} from './application';
/**
* Export the OpenAPI spec from the application
*/
async function exportOpenApiSpec(): Promise<void> {
const config: ApplicationConfig = {
rest: {
port: +(process.env.PORT ?? 3000),
host: process.env.HOST ?? 'localhost',
},
};
const outFile = process.argv[2] ?? '';
const app = new SzakdolgozatBackendApplication(config);
await app.boot();
await app.exportOpenApiSpec(outFile);
}
exportOpenApiSpec().catch(err => {
console.error('Fail to export OpenAPI spec from the application.', err);
process.exit(1);
});

View file

@ -0,0 +1,3 @@
# Repositories
This directory contains code for repositories provided by this app.

3
backend/src/sequence.ts Normal file
View file

@ -0,0 +1,3 @@
import {MiddlewareSequence} from '@loopback/rest';
export class MySequence extends MiddlewareSequence {}

9
backend/tsconfig.json Normal file
View file

@ -0,0 +1,9 @@
{
"$schema": "http://json.schemastore.org/tsconfig",
"extends": "@loopback/build/config/tsconfig.common.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}

View file

@ -7,6 +7,10 @@ services:
- ./frontend:/home/node/app
environment:
- COMMAND=run
backend:
build: backend
ports:
- "8019:3000"
networks:
default:

View file

@ -1,5 +1,7 @@
FROM node:16
USER node
EXPOSE 4200
WORKDIR /home/node/app

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
if [ $COMMAND == "deploy" ]; then
if [ "$COMMAND" == "deploy" ]; then
npm run buildProd
npm install -g firebase-tools
firebase login --no-localhost