Merge branch 'master' of https://github.com/rishighan/threetwo-import-service
Signed-off-by: Rishi Ghan <rishi.ghan@gmail.com> # Conflicts: # package-lock.json # package.json # yarn.lock
This commit is contained in:
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
43
Dockerfile
Normal file
43
Dockerfile
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
FROM node:buster-slim
|
||||||
|
|
||||||
|
# Show all node logs
|
||||||
|
ENV NPM_CONFIG_LOGLEVEL warn
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
WORKDIR /threetwo-import-service
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get upgrade -y \
|
||||||
|
&& apt-get install -y \
|
||||||
|
bash git openssh-server \
|
||||||
|
ca-certificates \
|
||||||
|
gcc \
|
||||||
|
libgl1-mesa-glx \
|
||||||
|
python3 python3-pip \
|
||||||
|
qtbase5-dev \
|
||||||
|
wget \
|
||||||
|
xdg-utils \
|
||||||
|
xz-utils \
|
||||||
|
libvips-dev build-essential
|
||||||
|
|
||||||
|
RUN wget -nv -O- https://download.calibre-ebook.com/linux-installer.sh | sh /dev/stdin install_dir=/opt isolated=y && \
|
||||||
|
rm -rf /tmp/calibre-installer-cache
|
||||||
|
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
COPY moleculer.config.ts ./
|
||||||
|
COPY tsconfig.json ./
|
||||||
|
|
||||||
|
# Install Dependncies
|
||||||
|
RUN npm install -g typescript ts-node
|
||||||
|
RUN npm ci --silent
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build and cleanup
|
||||||
|
RUN npm run build \
|
||||||
|
&& npm prune
|
||||||
|
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
# Start server
|
||||||
|
CMD ["npm", "start"]
|
||||||
52
README.md
52
README.md
@@ -1,40 +1,24 @@
|
|||||||
[](https://moleculer.services)
|
|
||||||
|
|
||||||
# threetwo-import-service
|
# threetwo-import-service
|
||||||
This is a [Moleculer](https://moleculer.services/)-based microservices project. Generated with the [Moleculer CLI](https://moleculer.services/docs/0.14/moleculer-cli.html).
|
|
||||||
|
|
||||||
## Usage
|
This [moleculer-based](https://github.com/moleculerjs/moleculer-web) microservice houses endpoints for the following functions:
|
||||||
Start the project with `npm run dev` command.
|
|
||||||
After starting, open the http://localhost:3000/ URL in your browser.
|
|
||||||
On the welcome page you can test the generated services via API Gateway and check the nodes & services.
|
|
||||||
|
|
||||||
In the terminal, try the following commands:
|
1. Local import of a comic library into mongo (currently supports `cbr` and `cbz` files)
|
||||||
- `nodes` - List all connected nodes.
|
2. Metadata extraction from file, `comicinfo.xml`
|
||||||
- `actions` - List all registered service actions.
|
3. Mongo comic object orchestration
|
||||||
- `call greeter.hello` - Call the `greeter.hello` action.
|
4. CRUD operations on `Comic` model
|
||||||
- `call greeter.welcome --name John` - Call the `greeter.welcome` action with the `name` parameter.
|
5. Helper utils to help with image metadata extraction, file operations and more.
|
||||||
- `call products.list` - List the products (call the `products.list` action).
|
|
||||||
|
|
||||||
|
## Local Development
|
||||||
|
|
||||||
## Services
|
1. You need `calibre` in your local path.
|
||||||
- **api**: API Gateway services
|
On `macOS` you can `brew install calibre` and make sure that `ebook-meta` is present on the path
|
||||||
- **greeter**: Sample service with `hello` and `welcome` actions.
|
2. You need `mongo` for the data store. on `macOS` you can use [these instructions](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/) to install it
|
||||||
- **products**: Sample DB service. To use with MongoDB, set `MONGO_URI` environment variables and install MongoDB adapter with `npm i moleculer-db-adapter-mongo`.
|
3. Clone this repo
|
||||||
|
4. Run `npm i`
|
||||||
|
5. Assuming you installed mongo correctly, run `MONGO_URI=mongodb://localhost:27017/threetwo npm run dev` to start the service
|
||||||
|
6. You should see the service spin up and a list of all the endpoints in the terminal
|
||||||
|
7. The service can be accessed through `http://localhost:3000/api/import/*`
|
||||||
|
## Docker Instructions
|
||||||
|
|
||||||
## Mixins
|
1. Build the image using `docker build . -t frishi/threetwo-import-service`. Give it a hot minute.
|
||||||
- **db.mixin**: Database access mixin for services. Based on [moleculer-db](https://github.com/moleculerjs/moleculer-db#readme)
|
2. Run it using `docker run -it frishi/threetwo-import-service`
|
||||||
|
|
||||||
|
|
||||||
## Useful links
|
|
||||||
|
|
||||||
* Moleculer website: https://moleculer.services/
|
|
||||||
* Moleculer Documentation: https://moleculer.services/docs/0.14/
|
|
||||||
|
|
||||||
## NPM scripts
|
|
||||||
|
|
||||||
- `npm run dev`: Start development mode (load all services locally with hot-reload & REPL)
|
|
||||||
- `npm run start`: Start production mode (set `SERVICES` env variable to load certain services)
|
|
||||||
- `npm run cli`: Start a CLI and connect to production. Don't forget to set production namespace with `--ns` argument in script
|
|
||||||
- `npm run lint`: Run ESLint
|
|
||||||
- `npm run ci`: Run continuous test mode with watching
|
|
||||||
- `npm test`: Run tests & generate coverage report
|
|
||||||
|
|||||||
@@ -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:
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
export interface IFolderResponse {
|
|
||||||
data: Array<IFolderData>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IExtractionOptions {
|
|
||||||
extractTarget: string;
|
|
||||||
sourceFolder: string;
|
|
||||||
targetExtractionFolder: string;
|
|
||||||
paginationOptions: IPaginationOptions;
|
|
||||||
extractionMode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPaginationOptions {
|
|
||||||
pageLimit: number;
|
|
||||||
page: number;
|
|
||||||
}
|
|
||||||
export interface IComicVineSearchMatch {
|
|
||||||
description: string;
|
|
||||||
id: number;
|
|
||||||
volumes: string;
|
|
||||||
}
|
|
||||||
export interface IFolderData {
|
|
||||||
name: string;
|
|
||||||
extension: string;
|
|
||||||
containedIn: string;
|
|
||||||
isFile: boolean;
|
|
||||||
isLink: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IExplodedPathResponse {
|
|
||||||
exploded: Array<string>;
|
|
||||||
fileName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IComicBookCoverMetadata {
|
|
||||||
name: string;
|
|
||||||
path: string;
|
|
||||||
containedIn: string;
|
|
||||||
fileSize: string;
|
|
||||||
imageHash: string;
|
|
||||||
dimensions: {
|
|
||||||
width: string;
|
|
||||||
height: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IExtractedComicBookCoverFile {
|
|
||||||
name: string;
|
|
||||||
path: string;
|
|
||||||
fileSize: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IExtractComicBookCoverErrorResponse {
|
|
||||||
message: string;
|
|
||||||
errorCode: string;
|
|
||||||
data: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
pass: process.env.MONGO_INITDB_ROOT_PASSWORD,
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
}),
|
}),
|
||||||
model: model,
|
model,
|
||||||
collection
|
collection,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
mkdir(path.resolve("./data"));
|
mkdir(path.resolve("./data"));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -157,15 +157,36 @@ const brokerConfig: BrokerOptions = {
|
|||||||
// Available built-in reporters: "Console", "CSV", "Event", "Prometheus", "Datadog", "StatsD"
|
// Available built-in reporters: "Console", "CSV", "Event", "Prometheus", "Datadog", "StatsD"
|
||||||
reporter: {
|
reporter: {
|
||||||
type: "Console",
|
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
|
// Enable built-in tracing function. More info: https://moleculer.services/docs/0.14/tracing.html
|
||||||
tracing: {
|
tracing: {
|
||||||
enabled: true,
|
enabled: false,
|
||||||
// Available built-in exporters: "Console", "Datadog", "Event", "EventLegacy", "Jaeger", "Zipkin"
|
// Available built-in exporters: "Console", "Datadog", "Event", "EventLegacy", "Jaeger", "Zipkin"
|
||||||
exporter: {
|
exporter: {
|
||||||
type: "Console", // Console exporter is only for development!
|
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,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
4202
package-lock.json
generated
4202
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "threetwo-import-service",
|
"name": "threetwo-library-service",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "My Moleculer-based microservices project",
|
"description": "My Moleculer-based microservices project",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -9,7 +9,10 @@
|
|||||||
"cli": "moleculer connect NATS",
|
"cli": "moleculer connect NATS",
|
||||||
"ci": "jest --watch",
|
"ci": "jest --watch",
|
||||||
"test": "jest --coverage",
|
"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": [
|
"keywords": [
|
||||||
"microservices",
|
"microservices",
|
||||||
@@ -27,7 +30,6 @@
|
|||||||
"jest": "^25.1.0",
|
"jest": "^25.1.0",
|
||||||
"jest-cli": "^25.1.0",
|
"jest-cli": "^25.1.0",
|
||||||
"moleculer-repl": "^0.6.2",
|
"moleculer-repl": "^0.6.2",
|
||||||
"threetwo-ui-typings": "^1.0.2-0",
|
|
||||||
"ts-jest": "^25.3.0",
|
"ts-jest": "^25.3.0",
|
||||||
"ts-node": "^8.8.1"
|
"ts-node": "^8.8.1"
|
||||||
},
|
},
|
||||||
@@ -43,6 +45,7 @@
|
|||||||
"imghash": "^0.0.9",
|
"imghash": "^0.0.9",
|
||||||
"leven": "^3.1.0",
|
"leven": "^3.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"mkdirp": "^0.5.5",
|
||||||
"moleculer": "^0.14.16",
|
"moleculer": "^0.14.16",
|
||||||
"moleculer-db": "^0.8.13",
|
"moleculer-db": "^0.8.13",
|
||||||
"moleculer-db-adapter-mongo": "^0.4.7",
|
"moleculer-db-adapter-mongo": "^0.4.7",
|
||||||
@@ -59,9 +62,8 @@
|
|||||||
"sharp": "^0.28.1",
|
"sharp": "^0.28.1",
|
||||||
"socket.io": "^4.1.1",
|
"socket.io": "^4.1.1",
|
||||||
"socket.io-stream": "^0.5.3",
|
"socket.io-stream": "^0.5.3",
|
||||||
"string-similarity": "^4.0.4",
|
"threetwo-ui-typings": "^1.0.3",
|
||||||
"typescript": "^3.8.3",
|
"typescript": "^3.8.3",
|
||||||
"unrar": "github:cnboker/node-unrar",
|
|
||||||
"xml2js": "^0.4.23"
|
"xml2js": "^0.4.23"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,minimal-ui">
|
<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://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 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"/>
|
<link rel="shortcut icon" type="image/png" href="https://moleculer.services/icon/favicon-16x16.png"/>
|
||||||
@@ -519,7 +519,6 @@
|
|||||||
menu: [
|
menu: [
|
||||||
{ id: "home", caption: "Home" },
|
{ id: "home", caption: "Home" },
|
||||||
{ id: "greeter", caption: "Greeter service" },
|
{ id: "greeter", caption: "Greeter service" },
|
||||||
{ id: "products", caption: "Products service" },
|
|
||||||
{ id: "nodes", caption: "Nodes" },
|
{ id: "nodes", caption: "Nodes" },
|
||||||
{ id: "services", caption: "Services" }
|
{ id: "services", caption: "Services" }
|
||||||
],
|
],
|
||||||
@@ -532,48 +531,11 @@
|
|||||||
{ id: "welcome", action: "greeter.welcome", rest: "/api/greeter/welcome", fields: [
|
{ id: "welcome", action: "greeter.welcome", rest: "/api/greeter/welcome", fields: [
|
||||||
{ field: "name", label: "Name", type: "text", paramType: "param", model: "welcomeName" }
|
{ field: "name", label: "Name", type: "text", paramType: "param", model: "welcomeName" }
|
||||||
], response: null, status: null, duration: null }
|
], 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: {
|
fields: {
|
||||||
welcomeName: "John",
|
welcomeName: "John"
|
||||||
productID: null,
|
|
||||||
productName: "Xiamoi Mi 9T",
|
|
||||||
productPrice: 299,
|
|
||||||
productValue: 1
|
|
||||||
},
|
},
|
||||||
|
|
||||||
broker: null,
|
broker: null,
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ export default class ApiService extends Service {
|
|||||||
// More info about settings: https://moleculer.services/docs/0.14/moleculer-web.html
|
// More info about settings: https://moleculer.services/docs/0.14/moleculer-web.html
|
||||||
settings: {
|
settings: {
|
||||||
port: process.env.PORT || 3000,
|
port: process.env.PORT || 3000,
|
||||||
|
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: "/api",
|
path: "/api",
|
||||||
whitelist: [
|
whitelist: [
|
||||||
// Access to any actions in all services under "/api" URL
|
|
||||||
"**",
|
"**",
|
||||||
],
|
],
|
||||||
cors: {
|
cors: {
|
||||||
@@ -37,10 +37,10 @@ export default class ApiService extends Service {
|
|||||||
},
|
},
|
||||||
use: [],
|
use: [],
|
||||||
mergeParams: true,
|
mergeParams: true,
|
||||||
|
authentication: false,
|
||||||
|
authorization: false,
|
||||||
autoAliases: true,
|
autoAliases: true,
|
||||||
aliases: {},
|
aliases: {},
|
||||||
|
|
||||||
// Calling options. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Calling-options
|
|
||||||
callingOptions: {},
|
callingOptions: {},
|
||||||
|
|
||||||
bodyParsers: {
|
bodyParsers: {
|
||||||
@@ -70,11 +70,10 @@ export default class ApiService extends Service {
|
|||||||
logResponseData: null,
|
logResponseData: null,
|
||||||
assets: {
|
assets: {
|
||||||
folder: "public",
|
folder: "public",
|
||||||
|
// Options to `server-static` module
|
||||||
options: {},
|
options: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {},
|
|
||||||
events: {
|
events: {
|
||||||
"**"(payload, sender, event) {
|
"**"(payload, sender, event) {
|
||||||
if (this.io)
|
if (this.io)
|
||||||
@@ -85,6 +84,8 @@ export default class ApiService extends Service {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
methods: {},
|
||||||
started(): any {
|
started(): any {
|
||||||
// Create a Socket.IO instance, passing it our server
|
// Create a Socket.IO instance, passing it our server
|
||||||
this.io = IO.listen(this.server);
|
this.io = IO.listen(this.server);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
resizeImage,
|
resizeImage,
|
||||||
} from "../utils/imagetransformation.utils";
|
} from "../utils/imagetransformation.utils";
|
||||||
export default class ProductsService extends Service {
|
export default class ImageTransformation extends Service {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
public constructor(
|
public constructor(
|
||||||
public broker: ServiceBroker,
|
public broker: ServiceBroker,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { convertXMLToJSON } from "../utils/xml.utils";
|
|||||||
import https from "https";
|
import https from "https";
|
||||||
const ObjectId = require("mongoose").Types.ObjectId;
|
const ObjectId = require("mongoose").Types.ObjectId;
|
||||||
|
|
||||||
export default class ProductsService extends Service {
|
export default class ImportService extends Service {
|
||||||
public constructor(
|
public constructor(
|
||||||
public broker: ServiceBroker,
|
public broker: ServiceBroker,
|
||||||
schema: ServiceSchema<{}> = { name: "import" }
|
schema: ServiceSchema<{}> = { name: "import" }
|
||||||
@@ -219,14 +219,12 @@ export default class ProductsService extends Service {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getComicVineVolumeMetadata: (apiDetailURL) => {
|
getComicVineVolumeMetadata: apiDetailURL => new Promise((resolve, reject) => https
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
return https
|
|
||||||
.get(
|
.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`,
|
`${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 = "";
|
let data = "";
|
||||||
resp.on("data", (chunk) => {
|
resp.on("data", chunk => {
|
||||||
data += chunk;
|
data += chunk;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -239,12 +237,10 @@ export default class ProductsService extends Service {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.on("error", (err) => {
|
.on("error", err => {
|
||||||
console.log("Error: " + err.message);
|
console.log("Error: " + err.message);
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
})),
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
schema
|
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,
|
"sourceMap": true,
|
||||||
"pretty": true,
|
"pretty": true,
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"outDir": "dist",
|
"outDir": "dist"
|
||||||
},
|
},
|
||||||
"include": ["./**/*"],
|
"include": ["./**/*"],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
IExtractedComicBookCoverFile,
|
IExtractedComicBookCoverFile,
|
||||||
IExtractionOptions,
|
IExtractionOptions,
|
||||||
IFolderData,
|
IFolderData,
|
||||||
} from "../interfaces/folder.interface";
|
} from "threetwo-ui-typings";
|
||||||
import { logger } from "./logger.utils";
|
import { logger } from "./logger.utils";
|
||||||
import { includes, remove, indexOf } from "lodash";
|
import { includes, remove, indexOf } from "lodash";
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export const extractCoverFromFile = async (
|
|||||||
let result: string;
|
let result: string;
|
||||||
const targetCoverImageFilePath = path.resolve(constructedPaths.targetPath + "/" + walkedFolder.name + "_cover.jpg")
|
const targetCoverImageFilePath = path.resolve(constructedPaths.targetPath + "/" + walkedFolder.name + "_cover.jpg")
|
||||||
result = await calibre.run(
|
result = await calibre.run(
|
||||||
"ebook-meta",
|
`/opt/calibre/ebook-meta`,
|
||||||
[path.resolve(constructedPaths.inputFilePath)],
|
[path.resolve(constructedPaths.inputFilePath)],
|
||||||
{
|
{
|
||||||
getCover: targetCoverImageFilePath,
|
getCover: targetCoverImageFilePath,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
IExtractedComicBookCoverFile,
|
IExtractedComicBookCoverFile,
|
||||||
IExtractionOptions,
|
IExtractionOptions,
|
||||||
IFolderData,
|
IFolderData,
|
||||||
} from "../interfaces/folder.interface";
|
} from "threetwo-ui-typings";
|
||||||
const Validator = require("fastest-validator");
|
const Validator = require("fastest-validator");
|
||||||
import { logger } from "./logger.utils";
|
import { logger } from "./logger.utils";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user