🐳 Switched to the TS template with the correct docker-compose config
This commit is contained in:
15
Dockerfile
15
Dockerfile
@@ -2,10 +2,8 @@ FROM node:buster-slim
|
||||
|
||||
# Show all node logs
|
||||
ENV NPM_CONFIG_LOGLEVEL warn
|
||||
ENV SERVICES=./dist/services
|
||||
ENV MONGO_URI=mongodb://localhost:27017/threetwo
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN mkdir /threetwo-import-service
|
||||
WORKDIR /threetwo-import-service
|
||||
|
||||
RUN apt-get update \
|
||||
@@ -29,12 +27,17 @@ COPY package.json package-lock.json ./
|
||||
COPY moleculer.config.ts ./
|
||||
COPY tsconfig.json ./
|
||||
|
||||
RUN npm cache verify
|
||||
# Install Dependncies
|
||||
RUN npm install -g typescript ts-node
|
||||
RUN npm install
|
||||
RUN npm ci --silent
|
||||
|
||||
COPY . .
|
||||
|
||||
# Build and cleanup
|
||||
RUN npm run build \
|
||||
&& npm prune
|
||||
|
||||
|
||||
EXPOSE 3000
|
||||
CMD npm start
|
||||
# Start server
|
||||
CMD ["npm", "start"]
|
||||
@@ -11,4 +11,4 @@ This microservice houses endpoints for the following functions:
|
||||
## Docker Instructions
|
||||
|
||||
1. Build the image using `docker build . -t frishi/threetwo-import-service`. Give it a hot minute.
|
||||
2. Run it using `docker run -it frishi/threetwo-import-service`
|
||||
2. Run it using `docker run -it frishi/threetwo-import-service`
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{"name":"Samsung Galaxy S10 Plus","quantity":10,"price":704,"_id":"MXoybiJcoo4K1Rmm"}
|
||||
{"name":"iPhone 11 Pro","quantity":25,"price":999,"_id":"2icANVEMoCj6nGjG"}
|
||||
{"name":"Huawei P30 Pro","quantity":15,"price":679,"_id":"6KE9pJvNQ83ez7qi"}
|
||||
9
docker-compose.env
Normal file
9
docker-compose.env
Normal file
@@ -0,0 +1,9 @@
|
||||
NAMESPACE=
|
||||
LOGGER=true
|
||||
LOGLEVEL=info
|
||||
SERVICEDIR=dist/services
|
||||
|
||||
TRANSPORTER=nats://nats:4222
|
||||
|
||||
CACHER=Memory
|
||||
|
||||
58
docker-compose.yml
Normal file
58
docker-compose.yml
Normal file
@@ -0,0 +1,58 @@
|
||||
version: "3.3"
|
||||
|
||||
services:
|
||||
|
||||
api:
|
||||
build:
|
||||
context: .
|
||||
image: threetwo-library-service
|
||||
env_file: docker-compose.env
|
||||
environment:
|
||||
SERVICES: api
|
||||
PORT: 3000
|
||||
depends_on:
|
||||
- nats
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.api-gw.rule=PathPrefix(`/`)"
|
||||
- "traefik.http.services.api-gw.loadbalancer.server.port=3000"
|
||||
networks:
|
||||
- internal
|
||||
|
||||
greeter:
|
||||
build:
|
||||
context: .
|
||||
image: threetwo-library-service
|
||||
env_file: docker-compose.env
|
||||
environment:
|
||||
SERVICES: greeter
|
||||
depends_on:
|
||||
- nats
|
||||
networks:
|
||||
- internal
|
||||
|
||||
nats:
|
||||
image: nats:2
|
||||
networks:
|
||||
- internal
|
||||
|
||||
traefik:
|
||||
image: traefik:v2.1
|
||||
command:
|
||||
- "--api.insecure=true" # Don't do that in production!
|
||||
- "--providers.docker=true"
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
ports:
|
||||
- 3000:80
|
||||
- 3001:8080
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
networks:
|
||||
- internal
|
||||
- default
|
||||
|
||||
networks:
|
||||
internal:
|
||||
|
||||
volumes:
|
||||
data:
|
||||
164
k8s.yaml
Normal file
164
k8s.yaml
Normal file
@@ -0,0 +1,164 @@
|
||||
#########################################################
|
||||
# Common Environment variables ConfigMap
|
||||
#########################################################
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: common-env
|
||||
data:
|
||||
NAMESPACE: ""
|
||||
LOGLEVEL: info
|
||||
SERVICEDIR: dist/services
|
||||
TRANSPORTER: nats://nats:4222
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
CACHER: Memory
|
||||
|
||||
|
||||
|
||||
---
|
||||
#########################################################
|
||||
# Service for Moleculer API Gateway service
|
||||
#########################################################
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: api
|
||||
spec:
|
||||
selector:
|
||||
app: api
|
||||
ports:
|
||||
- port: 3000
|
||||
targetPort: 3000
|
||||
|
||||
---
|
||||
#########################################################
|
||||
# Ingress for Moleculer API Gateway
|
||||
#########################################################
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ingress
|
||||
spec:
|
||||
rules:
|
||||
- host: threetwo-library-service.127.0.0.1.nip.io
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: api
|
||||
servicePort: 3000
|
||||
|
||||
---
|
||||
#########################################################
|
||||
# API Gateway service
|
||||
#########################################################
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: api
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: api
|
||||
replicas: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: api
|
||||
spec:
|
||||
containers:
|
||||
- name: api
|
||||
image: threetwo-library-service
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: common-env
|
||||
env:
|
||||
- name: SERVICES
|
||||
value: api
|
||||
- name: PORT
|
||||
value: "3000"
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
|
||||
---
|
||||
#########################################################
|
||||
# Greeter service
|
||||
#########################################################
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: greeter
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: greeter
|
||||
replicas: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: greeter
|
||||
spec:
|
||||
containers:
|
||||
- name: greeter
|
||||
image: threetwo-library-service
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: common-env
|
||||
env:
|
||||
- name: SERVICES
|
||||
value: greeter
|
||||
|
||||
|
||||
---
|
||||
#########################################################
|
||||
# NATS transporter service
|
||||
#########################################################
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nats
|
||||
spec:
|
||||
selector:
|
||||
app: nats
|
||||
ports:
|
||||
- port: 4222
|
||||
name: nats
|
||||
targetPort: 4222
|
||||
|
||||
---
|
||||
#########################################################
|
||||
# NATS transporter
|
||||
#########################################################
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nats
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nats
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nats
|
||||
spec:
|
||||
containers:
|
||||
- name: nats
|
||||
image: nats
|
||||
ports:
|
||||
- containerPort: 4222
|
||||
name: nats
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ export const DbMixin = (collection, model) => {
|
||||
pass: process.env.MONGO_INITDB_ROOT_PASSWORD,
|
||||
keepAlive: true,
|
||||
}),
|
||||
model: model,
|
||||
collection
|
||||
model,
|
||||
collection,
|
||||
};
|
||||
}
|
||||
}
|
||||
mkdir(path.resolve("./data"));
|
||||
};
|
||||
};
|
||||
|
||||
@@ -157,15 +157,36 @@ const brokerConfig: BrokerOptions = {
|
||||
// Available built-in reporters: "Console", "CSV", "Event", "Prometheus", "Datadog", "StatsD"
|
||||
reporter: {
|
||||
type: "Console",
|
||||
options: {
|
||||
// HTTP port
|
||||
port: 3030,
|
||||
// HTTP URL path
|
||||
path: "/metrics",
|
||||
// Default labels which are appended to all metrics labels
|
||||
defaultLabels: (registry: MetricRegistry) => ({
|
||||
namespace: registry.broker.namespace,
|
||||
nodeID: registry.broker.nodeID,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Enable built-in tracing function. More info: https://moleculer.services/docs/0.14/tracing.html
|
||||
tracing: {
|
||||
enabled: true,
|
||||
enabled: false,
|
||||
// Available built-in exporters: "Console", "Datadog", "Event", "EventLegacy", "Jaeger", "Zipkin"
|
||||
exporter: {
|
||||
type: "Console", // Console exporter is only for development!
|
||||
options: {
|
||||
// Custom logger
|
||||
logger: null,
|
||||
// Using colors
|
||||
colors: true,
|
||||
// Width of row
|
||||
width: 100,
|
||||
// Gauge width in the row
|
||||
gaugeWidth: 40,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
4053
package-lock.json
generated
4053
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -1,15 +1,18 @@
|
||||
{
|
||||
"name": "threetwo-import-service",
|
||||
"name": "threetwo-library-service",
|
||||
"version": "1.0.0",
|
||||
"description": "My Moleculer-based microservices project",
|
||||
"scripts": {
|
||||
"build": "tsc --build tsconfig.json",
|
||||
"dev": "ts-node ./node_modules/moleculer/bin/moleculer-runner.js --hot --repl --config moleculer.config.ts services/**/*.service.ts",
|
||||
"start": "./node_modules/moleculer/bin/moleculer-runner.js --config dist/moleculer.config.js",
|
||||
"start": "moleculer-runner --config dist/moleculer.config.js",
|
||||
"cli": "moleculer connect NATS",
|
||||
"ci": "jest --watch",
|
||||
"test": "jest --coverage",
|
||||
"lint": "eslint --ext .js,.ts ."
|
||||
"lint": "eslint --ext .js,.ts .",
|
||||
"dc:up": "docker-compose up --build -d",
|
||||
"dc:logs": "docker-compose logs -f",
|
||||
"dc:down": "docker-compose down"
|
||||
},
|
||||
"keywords": [
|
||||
"microservices",
|
||||
@@ -27,7 +30,6 @@
|
||||
"jest": "^25.1.0",
|
||||
"jest-cli": "^25.1.0",
|
||||
"moleculer-repl": "^0.6.2",
|
||||
"threetwo-ui-typings": "^1.0.3",
|
||||
"ts-jest": "^25.3.0",
|
||||
"ts-node": "^8.8.1"
|
||||
},
|
||||
@@ -42,6 +44,7 @@
|
||||
"imghash": "^0.0.9",
|
||||
"leven": "^3.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mkdirp": "^0.5.5",
|
||||
"moleculer": "^0.14.16",
|
||||
"moleculer-db": "^0.8.13",
|
||||
"moleculer-db-adapter-mongo": "^0.4.7",
|
||||
@@ -58,6 +61,7 @@
|
||||
"sharp": "^0.28.1",
|
||||
"socket.io": "^4.1.1",
|
||||
"socket.io-stream": "^0.5.3",
|
||||
"threetwo-ui-typings": "^1.0.3",
|
||||
"typescript": "^3.8.3",
|
||||
"xml2js": "^0.4.23"
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,minimal-ui">
|
||||
<title>threetwo-import-service - Moleculer Microservices Project</title>
|
||||
<title>threetwo-library-service - Moleculer Microservices Project</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700" rel="stylesheet">
|
||||
<link href="https://unpkg.com/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link rel="shortcut icon" type="image/png" href="https://moleculer.services/icon/favicon-16x16.png"/>
|
||||
@@ -519,7 +519,6 @@
|
||||
menu: [
|
||||
{ id: "home", caption: "Home" },
|
||||
{ id: "greeter", caption: "Greeter service" },
|
||||
{ id: "products", caption: "Products service" },
|
||||
{ id: "nodes", caption: "Nodes" },
|
||||
{ id: "services", caption: "Services" }
|
||||
],
|
||||
@@ -532,48 +531,11 @@
|
||||
{ id: "welcome", action: "greeter.welcome", rest: "/api/greeter/welcome", fields: [
|
||||
{ field: "name", label: "Name", type: "text", paramType: "param", model: "welcomeName" }
|
||||
], response: null, status: null, duration: null }
|
||||
],
|
||||
products: [
|
||||
{ id: "list", action: "products.list", rest: "/api/products/", response: null, status: null, duration: null, afterResponse: response => !this.fields.productID && (this.fields.productID = response.rows[0]._id) },
|
||||
|
||||
{ id: "create", action: "products.create", rest: "/api/products/", method: "POST", fields: [
|
||||
{ field: "name", label: "Name", type: "text", paramType: "body", model: "productName" },
|
||||
{ field: "price", label: "Price", type: "number", paramType: "body", model: "productPrice" },
|
||||
], response: null, status: null, duration: null, afterResponse: response => this.fields.productID = response._id },
|
||||
|
||||
{ id: "get", action: "products.get", rest: "/api/products/:id", method: "GET", fields: [
|
||||
{ field: "id", label: "ID", type: "text", paramType: "url", model: "productID" }
|
||||
], response: null, status: null, duration: null },
|
||||
|
||||
{ id: "update", action: "products.update", rest: "/api/products/:id", method: "PUT", fields: [
|
||||
{ field: "id", label: "ID", type: "text", paramType: "url", model: "productID" },
|
||||
{ field: "name", label: "Name", type: "text", paramType: "body", model: "productName" },
|
||||
{ field: "price", label: "Price", type: "number", paramType: "body", model: "productPrice" },
|
||||
], response: null, status: null, duration: null },
|
||||
|
||||
{ id: "increase", action: "products.increaseQuantity", rest: "/api/products/:id/quantity/increase", method: "PUT", fields: [
|
||||
{ field: "id", label: "ID", type: "text", paramType: "url", model: "productID" },
|
||||
{ field: "value", label: "Value", type: "number", paramType: "body", model: "productValue" },
|
||||
], response: null, status: null, duration: null },
|
||||
|
||||
{ id: "decrease", action: "products.decreaseQuantity", rest: "/api/products/:id/quantity/decrease", method: "PUT", fields: [
|
||||
{ field: "id", label: "ID", type: "text", paramType: "url", model: "productID" },
|
||||
{ field: "value", label: "Value", type: "number", paramType: "body", model: "productValue" },
|
||||
], response: null, status: null, duration: null },
|
||||
|
||||
{ id: "delete", action: "products.delete", rest: "/api/products/:id", method: "DELETE", fields: [
|
||||
{ field: "id", label: "ID", type: "text", paramType: "url", model: "productID" }
|
||||
], response: null, status: null, duration: null },
|
||||
|
||||
]
|
||||
},
|
||||
|
||||
fields: {
|
||||
welcomeName: "John",
|
||||
productID: null,
|
||||
productName: "Xiamoi Mi 9T",
|
||||
productPrice: 299,
|
||||
productValue: 1
|
||||
welcomeName: "John"
|
||||
},
|
||||
|
||||
broker: null,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { map } from "lodash";
|
||||
const IO = require("socket.io")();
|
||||
|
||||
export default class ApiService extends Service {
|
||||
|
||||
public constructor(broker: ServiceBroker) {
|
||||
super(broker);
|
||||
// @ts-ignore
|
||||
@@ -14,67 +15,104 @@ export default class ApiService extends Service {
|
||||
// More info about settings: https://moleculer.services/docs/0.14/moleculer-web.html
|
||||
settings: {
|
||||
port: process.env.PORT || 3000,
|
||||
routes: [
|
||||
{
|
||||
path: "/api",
|
||||
whitelist: [
|
||||
// Access to any actions in all services under "/api" URL
|
||||
"**",
|
||||
|
||||
routes: [{
|
||||
path: "/api",
|
||||
whitelist: [
|
||||
// Access to any actions in all services under "/api" URL
|
||||
"**",
|
||||
],
|
||||
cors: {
|
||||
origin: "*",
|
||||
methods: [
|
||||
"GET",
|
||||
"OPTIONS",
|
||||
"POST",
|
||||
"PUT",
|
||||
"DELETE",
|
||||
],
|
||||
cors: {
|
||||
origin: "*",
|
||||
methods: [
|
||||
"GET",
|
||||
"OPTIONS",
|
||||
"POST",
|
||||
"PUT",
|
||||
"DELETE",
|
||||
],
|
||||
allowedHeaders: ["*"],
|
||||
exposedHeaders: [],
|
||||
credentials: false,
|
||||
maxAge: 3600,
|
||||
},
|
||||
use: [],
|
||||
mergeParams: true,
|
||||
autoAliases: true,
|
||||
aliases: {},
|
||||
allowedHeaders: ["*"],
|
||||
exposedHeaders: [],
|
||||
credentials: false,
|
||||
maxAge: 3600,
|
||||
},
|
||||
// Route-level Express middlewares. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Middlewares
|
||||
use: [],
|
||||
// Enable/disable parameter merging method. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Disable-merging
|
||||
mergeParams: true,
|
||||
|
||||
// Calling options. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Calling-options
|
||||
callingOptions: {},
|
||||
// Enable authentication. Implement the logic into `authenticate` method. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Authentication
|
||||
authentication: false,
|
||||
|
||||
bodyParsers: {
|
||||
json: {
|
||||
strict: false,
|
||||
limit: "1MB",
|
||||
},
|
||||
urlencoded: {
|
||||
extended: true,
|
||||
limit: "1MB",
|
||||
},
|
||||
// Enable authorization. Implement the logic into `authorize` method. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Authorization
|
||||
authorization: false,
|
||||
|
||||
// The auto-alias feature allows you to declare your route alias directly in your services.
|
||||
// The gateway will dynamically build the full routes from service schema.
|
||||
autoAliases: true,
|
||||
|
||||
aliases:{},
|
||||
/**
|
||||
* Before call hook. You can check the request.
|
||||
* @param {Context} ctx
|
||||
* @param {Object} route
|
||||
* @param {IncomingMessage} req
|
||||
* @param {ServerResponse} res
|
||||
* @param {Object} data
|
||||
onBeforeCall(ctx: Context<any,{userAgent: string}>,
|
||||
route: object, req: IncomingMessage, res: ServerResponse) {
|
||||
Set request headers to context meta
|
||||
ctx.meta.userAgent = req.headers["user-agent"];
|
||||
},
|
||||
*/
|
||||
|
||||
/**
|
||||
* After call hook. You can modify the data.
|
||||
* @param {Context} ctx
|
||||
* @param {Object} route
|
||||
* @param {IncomingMessage} req
|
||||
* @param {ServerResponse} res
|
||||
* @param {Object} data
|
||||
*
|
||||
onAfterCall(ctx: Context, route: object, req: IncomingMessage, res: ServerResponse, data: object) {
|
||||
// Async function which return with Promise
|
||||
return doSomething(ctx, res, data);
|
||||
},
|
||||
*/
|
||||
|
||||
// Calling options. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Calling-options
|
||||
callingOptions: {},
|
||||
|
||||
bodyParsers: {
|
||||
json: {
|
||||
strict: false,
|
||||
limit: "1MB",
|
||||
},
|
||||
urlencoded: {
|
||||
extended: true,
|
||||
limit: "1MB",
|
||||
},
|
||||
mappingPolicy: "all", // Available values: "all", "restrict"
|
||||
logging: true,
|
||||
},
|
||||
{
|
||||
path: "/userdata",
|
||||
use: [ApiGateway.serveStatic("userdata")],
|
||||
},
|
||||
{
|
||||
path: "/comics",
|
||||
use: [ApiGateway.serveStatic("comics")],
|
||||
},
|
||||
],
|
||||
|
||||
// Mapping policy setting. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Mapping-policy
|
||||
mappingPolicy: "all", // Available values: "all", "restrict"
|
||||
|
||||
// Enable/disable logging
|
||||
logging: true,
|
||||
}],
|
||||
// Do not log client side errors (does not log an error response when the error.code is 400<=X<500)
|
||||
log4XXResponses: false,
|
||||
// Logging the request parameters. Set to any log level to enable it. E.g. "info"
|
||||
logRequestParams: null,
|
||||
// Logging the response data. Set to any log level to enable it. E.g. "info"
|
||||
logResponseData: null,
|
||||
// Serve assets from "public" folder
|
||||
assets: {
|
||||
folder: "public",
|
||||
// Options to `server-static` module
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
|
||||
methods: {},
|
||||
events: {
|
||||
"**"(payload, sender, event) {
|
||||
if (this.io)
|
||||
@@ -85,6 +123,77 @@ export default class ApiService extends Service {
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
/**
|
||||
* Authenticate the request. It checks the `Authorization` token value in the request header.
|
||||
* Check the token value & resolve the user by the token.
|
||||
* The resolved user will be available in `ctx.meta.user`
|
||||
*
|
||||
* PLEASE NOTE, IT'S JUST AN EXAMPLE IMPLEMENTATION. DO NOT USE IN PRODUCTION!
|
||||
*
|
||||
* @param {Context} ctx
|
||||
* @param {any} route
|
||||
* @param {IncomingMessage} req
|
||||
* @returns {Promise}
|
||||
|
||||
async authenticate (ctx: Context, route: any, req: IncomingMessage): Promise < any > => {
|
||||
// Read the token from header
|
||||
const auth = req.headers.authorization;
|
||||
|
||||
if (auth && auth.startsWith("Bearer")) {
|
||||
const token = auth.slice(7);
|
||||
|
||||
// Check the token. Tip: call a service which verify the token. E.g. `accounts.resolveToken`
|
||||
if (token === "123456") {
|
||||
// Returns the resolved user. It will be set to the `ctx.meta.user`
|
||||
return {
|
||||
id: 1,
|
||||
name: "John Doe",
|
||||
};
|
||||
|
||||
} else {
|
||||
// Invalid token
|
||||
throw new ApiGateway.Errors.UnAuthorizedError(ApiGateway.Errors.ERR_INVALID_TOKEN, {
|
||||
error: "Invalid Token",
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
// No token. Throw an error or do nothing if anonymous access is allowed.
|
||||
// Throw new E.UnAuthorizedError(E.ERR_NO_TOKEN);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
*/
|
||||
|
||||
/**
|
||||
* Authorize the request. Check that the authenticated user has right to access the resource.
|
||||
*
|
||||
* PLEASE NOTE, IT'S JUST AN EXAMPLE IMPLEMENTATION. DO NOT USE IN PRODUCTION!
|
||||
*
|
||||
* @param {Context} ctx
|
||||
* @param {Object} route
|
||||
* @param {IncomingMessage} req
|
||||
* @returns {Promise}
|
||||
|
||||
async authorize (ctx: Context < any, {
|
||||
user: string;
|
||||
} > , route: Record<string, undefined>, req: IncomingMessage): Promise < any > => {
|
||||
// Get the authenticated user.
|
||||
const user = ctx.meta.user;
|
||||
|
||||
// It check the `auth` property in action schema.
|
||||
// @ts-ignore
|
||||
if (req.$action.auth === "required" && !user) {
|
||||
throw new ApiGateway.Errors.UnAuthorizedError("NO_RIGHTS", {
|
||||
error: "Unauthorized",
|
||||
});
|
||||
}
|
||||
},
|
||||
*/
|
||||
},
|
||||
started(): any {
|
||||
// Create a Socket.IO instance, passing it our server
|
||||
this.io = IO.listen(this.server);
|
||||
@@ -141,6 +250,7 @@ export default class ApiService extends Service {
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,14 +219,12 @@ export default class ImportService extends Service {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getComicVineVolumeMetadata: (apiDetailURL) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return https
|
||||
getComicVineVolumeMetadata: apiDetailURL => new Promise((resolve, reject) => https
|
||||
.get(
|
||||
`${apiDetailURL}?api_key=a5fa0663683df8145a85d694b5da4b87e1c92c69&format=json&limit=1&offset=0&field_list=id,name,description,image,first_issue,last_issue,publisher,count_of_issues,character_credits,person_credits,aliases`,
|
||||
(resp) => {
|
||||
resp => {
|
||||
let data = "";
|
||||
resp.on("data", (chunk) => {
|
||||
resp.on("data", chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
@@ -239,12 +237,10 @@ export default class ImportService extends Service {
|
||||
});
|
||||
}
|
||||
)
|
||||
.on("error", (err) => {
|
||||
.on("error", err => {
|
||||
console.log("Error: " + err.message);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
schema
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
import { ServiceBroker } from "moleculer";
|
||||
import TestService from "../../services/products.service";
|
||||
|
||||
describe("Test 'products' service", () => {
|
||||
|
||||
describe("Test actions", () => {
|
||||
const broker = new ServiceBroker({ logger: false });
|
||||
const service = broker.createService(TestService);
|
||||
service.seedDB = null; // Disable seeding
|
||||
|
||||
beforeAll(() => broker.start());
|
||||
afterAll(() => broker.stop());
|
||||
|
||||
const record = {
|
||||
name: "Awesome item",
|
||||
price: 999,
|
||||
};
|
||||
let newID: string;
|
||||
|
||||
it("should contains the seeded items", async () => {
|
||||
const res = await broker.call("products.list");
|
||||
expect(res).toEqual({ page: 1, pageSize: 10, rows: [], total: 0, totalPages: 0 });
|
||||
});
|
||||
|
||||
it("should add the new item", async () => {
|
||||
const res: any = await broker.call("products.create", record);
|
||||
expect(res).toEqual({
|
||||
_id: expect.any(String),
|
||||
name: "Awesome item",
|
||||
price: 999,
|
||||
quantity: 0,
|
||||
});
|
||||
newID = res._id;
|
||||
|
||||
const res2 = await broker.call("products.count");
|
||||
expect(res2).toBe(1);
|
||||
});
|
||||
|
||||
it("should get the saved item", async () => {
|
||||
const res = await broker.call("products.get", { id: newID });
|
||||
expect(res).toEqual({
|
||||
_id: expect.any(String),
|
||||
name: "Awesome item",
|
||||
price: 999,
|
||||
quantity: 0,
|
||||
});
|
||||
|
||||
const res2 = await broker.call("products.list");
|
||||
expect(res2).toEqual({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
rows: [{ _id: newID, name: "Awesome item", price: 999, quantity: 0 }],
|
||||
total: 1,
|
||||
totalPages: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it("should update an item", async () => {
|
||||
const res = await broker.call("products.update", { id: newID, price: 499 });
|
||||
expect(res).toEqual({
|
||||
_id: expect.any(String),
|
||||
name: "Awesome item",
|
||||
price: 499,
|
||||
quantity: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("should get the updated item", async () => {
|
||||
const res = await broker.call("products.get", { id: newID });
|
||||
expect(res).toEqual({
|
||||
_id: expect.any(String),
|
||||
name: "Awesome item",
|
||||
price: 499,
|
||||
quantity: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("should increase the quantity", async () => {
|
||||
const res = await broker.call("products.increaseQuantity", { id: newID, value: 5 });
|
||||
expect(res).toEqual({
|
||||
_id: expect.any(String),
|
||||
name: "Awesome item",
|
||||
price: 499,
|
||||
quantity: 5,
|
||||
});
|
||||
});
|
||||
|
||||
it("should decrease the quantity", async () => {
|
||||
const res = await broker.call("products.decreaseQuantity", { id: newID, value: 2 });
|
||||
expect(res).toEqual({
|
||||
_id: expect.any(String),
|
||||
name: "Awesome item",
|
||||
price: 499,
|
||||
quantity: 3,
|
||||
});
|
||||
});
|
||||
|
||||
it("should remove the updated item", async () => {
|
||||
const res = await broker.call("products.remove", { id: newID });
|
||||
expect(res).toBe(1);
|
||||
|
||||
const res2 = await broker.call("products.count");
|
||||
expect(res2).toBe(0);
|
||||
|
||||
const res3 = await broker.call("products.list");
|
||||
expect(res3).toEqual({ page: 1, pageSize: 10, rows: [], total: 0, totalPages: 0 });
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,180 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
import { Context, Errors, ServiceBroker } from "moleculer";
|
||||
import TestService from "../../../services/products.service";
|
||||
|
||||
describe("Test 'products' service", () => {
|
||||
|
||||
describe("Test actions", () => {
|
||||
const broker = new ServiceBroker({ logger: false });
|
||||
const service = broker.createService(TestService);
|
||||
|
||||
jest.spyOn(service.adapter, "updateById");
|
||||
jest.spyOn(service, "transformDocuments");
|
||||
jest.spyOn(service, "entityChanged");
|
||||
|
||||
beforeAll(() => broker.start());
|
||||
afterAll(() => broker.stop());
|
||||
|
||||
const record = {
|
||||
_id: "123",
|
||||
name: "Awesome thing",
|
||||
price: 999,
|
||||
quantity: 25,
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
|
||||
describe("Test 'products.increaseQuantity'", () => {
|
||||
|
||||
it("should call the adapter updateById method & transform result", async () => {
|
||||
service.adapter.updateById.mockImplementation(async () => record);
|
||||
service.transformDocuments.mockClear();
|
||||
service.entityChanged.mockClear();
|
||||
|
||||
const res = await broker.call("products.increaseQuantity", {
|
||||
id: "123",
|
||||
value: 10,
|
||||
});
|
||||
expect(res).toEqual({
|
||||
_id: "123",
|
||||
name: "Awesome thing",
|
||||
price: 999,
|
||||
quantity: 25,
|
||||
});
|
||||
|
||||
expect(service.adapter.updateById).toBeCalledTimes(1);
|
||||
expect(service.adapter.updateById).toBeCalledWith("123", { $inc: { quantity: 10 } } );
|
||||
|
||||
expect(service.transformDocuments).toBeCalledTimes(1);
|
||||
expect(service.transformDocuments).toBeCalledWith(expect.any(Context), { id: "123", value: 10 }, record);
|
||||
|
||||
expect(service.entityChanged).toBeCalledTimes(1);
|
||||
expect(service.entityChanged).toBeCalledWith("updated", { _id: "123", name: "Awesome thing", price: 999, quantity: 25 }, expect.any(Context));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("Test 'products.decreaseQuantity'", () => {
|
||||
|
||||
it("should call the adapter updateById method & transform result", async () => {
|
||||
service.adapter.updateById.mockClear();
|
||||
service.transformDocuments.mockClear();
|
||||
service.entityChanged.mockClear();
|
||||
|
||||
const res = await broker.call("products.decreaseQuantity", {
|
||||
id: "123",
|
||||
value: 10,
|
||||
});
|
||||
expect(res).toEqual({
|
||||
_id: "123",
|
||||
name: "Awesome thing",
|
||||
price: 999,
|
||||
quantity: 25,
|
||||
});
|
||||
|
||||
expect(service.adapter.updateById).toBeCalledTimes(1);
|
||||
expect(service.adapter.updateById).toBeCalledWith("123", { $inc: { quantity: -10 } } );
|
||||
|
||||
expect(service.transformDocuments).toBeCalledTimes(1);
|
||||
expect(service.transformDocuments).toBeCalledWith(expect.any(Context), { id: "123", value: 10 }, record);
|
||||
|
||||
expect(service.entityChanged).toBeCalledTimes(1);
|
||||
expect(service.entityChanged).toBeCalledWith("updated", { _id: "123", name: "Awesome thing", price: 999, quantity: 25 }, expect.any(Context));
|
||||
});
|
||||
|
||||
it("should throw error if params is not valid", async () => {
|
||||
service.adapter.updateById.mockClear();
|
||||
service.transformDocuments.mockClear();
|
||||
service.entityChanged.mockClear();
|
||||
|
||||
expect.assertions(2);
|
||||
try {
|
||||
await broker.call("products.decreaseQuantity", {
|
||||
id: "123",
|
||||
value: -5,
|
||||
});
|
||||
} catch (err) {
|
||||
expect(err).toBeInstanceOf(Errors.ValidationError);
|
||||
expect(err.data).toEqual([{
|
||||
action: "products.decreaseQuantity",
|
||||
actual: -5,
|
||||
field: "value",
|
||||
message: "The 'value' field must be a positive number.",
|
||||
nodeID: broker.nodeID,
|
||||
type: "numberPositive",
|
||||
}]);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("Test methods", () => {
|
||||
const broker = new ServiceBroker({ logger: false });
|
||||
const service = broker.createService(TestService);
|
||||
|
||||
jest.spyOn(service.adapter, "insertMany");
|
||||
jest.spyOn(service, "seedDB");
|
||||
|
||||
beforeAll(() => broker.start());
|
||||
afterAll(() => broker.stop());
|
||||
|
||||
describe("Test 'seedDB'", () => {
|
||||
|
||||
it("should be called after service started & DB connected", async () => {
|
||||
expect(service.seedDB).toBeCalledTimes(1);
|
||||
expect(service.seedDB).toBeCalledWith();
|
||||
});
|
||||
|
||||
it("should insert 3 documents", async () => {
|
||||
expect(service.adapter.insertMany).toBeCalledTimes(1);
|
||||
expect(service.adapter.insertMany).toBeCalledWith([
|
||||
{ name: "Samsung Galaxy S10 Plus", quantity: 10, price: 704 },
|
||||
{ name: "iPhone 11 Pro", quantity: 25, price: 999 },
|
||||
{ name: "Huawei P30 Pro", quantity: 15, price: 679 },
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("Test hooks", () => {
|
||||
const broker = new ServiceBroker({ logger: false });
|
||||
const createActionFn = jest.fn();
|
||||
// @ts-ignore
|
||||
broker.createService(TestService, {
|
||||
actions: {
|
||||
create: {
|
||||
handler: createActionFn,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
beforeAll(() => broker.start());
|
||||
afterAll(() => broker.stop());
|
||||
|
||||
describe("Test before 'create' hook", () => {
|
||||
|
||||
it("should add quantity with zero", async () => {
|
||||
await broker.call("products.create", {
|
||||
id: "111",
|
||||
name: "Test product",
|
||||
price: 100,
|
||||
});
|
||||
|
||||
expect(createActionFn).toBeCalledTimes(1);
|
||||
expect(createActionFn.mock.calls[0][0].params).toEqual({
|
||||
id: "111",
|
||||
name: "Test product",
|
||||
price: 100,
|
||||
quantity: 0,
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -8,7 +8,7 @@
|
||||
"sourceMap": true,
|
||||
"pretty": true,
|
||||
"target": "es6",
|
||||
"outDir": "dist",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["./**/*"],
|
||||
"exclude": [
|
||||
|
||||
Reference in New Issue
Block a user