From 1b0cada8487de9c0a11a16091d11db7b33915a15 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Sat, 11 May 2024 13:31:10 -0400 Subject: [PATCH 01/15] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F=20Added=20validatio?= =?UTF-8?q?n=20to=20db=20mixin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mixins/db.mixin.ts | 70 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/mixins/db.mixin.ts b/mixins/db.mixin.ts index 95542c0..f8ffedd 100644 --- a/mixins/db.mixin.ts +++ b/mixins/db.mixin.ts @@ -2,21 +2,73 @@ const path = require("path"); const mkdir = require("mkdirp").sync; const DbService = require("moleculer-db"); - export const DbMixin = (collection, model) => { if (process.env.MONGO_URI) { const MongooseAdapter = require("moleculer-db-adapter-mongoose"); + console.log("Connecting to MongoDB at", process.env.MONGO_URI); + + const connectWithRetry = ( + adapter, + maxRetries = 5, + interval = 5000, + retries = 0 + ) => { + return adapter + .connect() + .then(() => console.log("MongoDB connected successfully!")) + .catch((err) => { + console.error("MongoDB connection error:", err); + if (retries < maxRetries) { + console.log( + `Retrying MongoDB connection in ${ + interval / 1000 + } seconds...` + ); + setTimeout( + () => + connectWithRetry( + adapter, + maxRetries, + interval, + retries + 1 + ), + interval + ); + } else { + console.error( + "Failed to connect to MongoDB after several attempts." + ); + } + }); + }; + + const adapter = new MongooseAdapter(process.env.MONGO_URI, { + user: process.env.MONGO_INITDB_ROOT_USERNAME, + pass: process.env.MONGO_INITDB_ROOT_PASSWORD, + keepAlive: true, + useNewUrlParser: true, + useUnifiedTopology: true, + }); + return { mixins: [DbService], - adapter: new MongooseAdapter(process.env.MONGO_URI, { - user: process.env.MONGO_INITDB_ROOT_USERNAME, - pass: process.env.MONGO_INITDB_ROOT_PASSWORD, - keepAlive: true, - useUnifiedTopology: true, - family: 4, - }), + adapter: adapter, model, + collection, + started() { + connectWithRetry(this.adapter); + }, + stopped() { + this.adapter + .disconnect() + .then(() => console.log("MongoDB disconnected")) + .catch((err) => + console.error("MongoDB disconnection error:", err) + ); + }, }; + } else { + console.log("MONGO_URI not provided, initializing local storage..."); + mkdir(path.resolve("./data")); } - mkdir(path.resolve("./data")); }; -- 2.49.1 From f4563c12c61f682a1b91f6828ee5fb2b1f8cfd04 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Sun, 12 May 2024 23:35:01 -0400 Subject: [PATCH 02/15] =?UTF-8?q?=F0=9F=94=A7=20Added=20startup=20scripts?= =?UTF-8?q?=20fixing=20MongoDB=20timeouts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mixins/db.mixin.ts | 117 ++++++++++------------ package-lock.json | 14 +-- package.json | 6 +- scripts/start.sh | 22 +++++ scripts/wait-for-it.sh | 190 ++++++++++++++++++++++++++++++++++++ services/library.service.ts | 8 +- 6 files changed, 279 insertions(+), 78 deletions(-) create mode 100755 scripts/start.sh create mode 100755 scripts/wait-for-it.sh diff --git a/mixins/db.mixin.ts b/mixins/db.mixin.ts index f8ffedd..c999dcf 100644 --- a/mixins/db.mixin.ts +++ b/mixins/db.mixin.ts @@ -3,72 +3,59 @@ const mkdir = require("mkdirp").sync; const DbService = require("moleculer-db"); export const DbMixin = (collection, model) => { - if (process.env.MONGO_URI) { - const MongooseAdapter = require("moleculer-db-adapter-mongoose"); - console.log("Connecting to MongoDB at", process.env.MONGO_URI); - - const connectWithRetry = ( - adapter, - maxRetries = 5, - interval = 5000, - retries = 0 - ) => { - return adapter - .connect() - .then(() => console.log("MongoDB connected successfully!")) - .catch((err) => { - console.error("MongoDB connection error:", err); - if (retries < maxRetries) { - console.log( - `Retrying MongoDB connection in ${ - interval / 1000 - } seconds...` - ); - setTimeout( - () => - connectWithRetry( - adapter, - maxRetries, - interval, - retries + 1 - ), - interval - ); - } else { - console.error( - "Failed to connect to MongoDB after several attempts." - ); - } - }); - }; - - const adapter = new MongooseAdapter(process.env.MONGO_URI, { - user: process.env.MONGO_INITDB_ROOT_USERNAME, - pass: process.env.MONGO_INITDB_ROOT_PASSWORD, - keepAlive: true, - useNewUrlParser: true, - useUnifiedTopology: true, - }); - - return { - mixins: [DbService], - adapter: adapter, - model, - collection, - started() { - connectWithRetry(this.adapter); - }, - stopped() { - this.adapter - .disconnect() - .then(() => console.log("MongoDB disconnected")) - .catch((err) => - console.error("MongoDB disconnection error:", err) - ); - }, - }; - } else { + if (!process.env.MONGO_URI) { console.log("MONGO_URI not provided, initializing local storage..."); mkdir(path.resolve("./data")); + return { mixins: [DbService] }; // Handle case where no DB URI is provided } + + const MongooseAdapter = require("moleculer-db-adapter-mongoose"); + const adapter = new MongooseAdapter(process.env.MONGO_URI, { + user: process.env.MONGO_INITDB_ROOT_USERNAME, + pass: process.env.MONGO_INITDB_ROOT_PASSWORD, + keepAlive: true, + useNewUrlParser: true, + useUnifiedTopology: true, + }); + + const connectWithRetry = async ( + adapter, + maxRetries = 5, + interval = 5000 + ) => { + for (let retry = 0; retry < maxRetries; retry++) { + try { + await adapter.connect(); + console.log("MongoDB connected successfully!"); + return; + } catch (err) { + console.error("MongoDB connection error:", err); + console.log( + `Retrying MongoDB connection in ${ + interval / 1000 + } seconds...` + ); + await new Promise((resolve) => setTimeout(resolve, interval)); + } + } + console.error("Failed to connect to MongoDB after several attempts."); + }; + + return { + mixins: [DbService], + adapter, + model, + collection, + async started() { + await connectWithRetry(this.adapter); + }, + async stopped() { + try { + await this.adapter.disconnect(); + console.log("MongoDB disconnected"); + } catch (err) { + console.error("MongoDB disconnection error:", err); + } + }, + }; }; diff --git a/package-lock.json b/package-lock.json index 671a70b..d978e66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "mkdirp": "^0.5.5", "moleculer-bullmq": "^3.0.0", "moleculer-db": "^0.8.23", - "moleculer-db-adapter-mongoose": "^0.9.2", + "moleculer-db-adapter-mongoose": "^0.9.4", "moleculer-io": "^2.2.0", "moleculer-web": "^0.10.5", "mongoosastic-ts": "^6.0.3", @@ -10178,9 +10178,9 @@ } }, "node_modules/moleculer-db-adapter-mongoose": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/moleculer-db-adapter-mongoose/-/moleculer-db-adapter-mongoose-0.9.3.tgz", - "integrity": "sha512-zv77GKBZrAUCnxUESxqyq58t9HUFxB9M0CrnFkxUr87HyqzRcj8A+sAkllFuK8sMBOdgDHSA5HUi7VrchrB2NQ==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/moleculer-db-adapter-mongoose/-/moleculer-db-adapter-mongoose-0.9.4.tgz", + "integrity": "sha512-+ZRPfyLAVbBb6St/9RAqiZdWY3hw++fPutwqSPg8AYNL7n/eIQRnvx7oXn+ebnezbuCXg63L1siT9uzyKNzbZw==", "dependencies": { "bluebird": "^3.7.2", "lodash": "^4.17.21" @@ -13882,9 +13882,9 @@ ] }, "node_modules/r2-lcp-js": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/r2-lcp-js/-/r2-lcp-js-1.0.39.tgz", - "integrity": "sha512-M4m1kBdMAYrHnDIGjBoxwDI+uKqbn/CEy4r2+D0Pca7OoMcHq1DQq1IF/bNnr/Ko6xyNyejaLTDgrr/pVbmEUA==", + "version": "1.0.40", + "resolved": "https://registry.npmjs.org/r2-lcp-js/-/r2-lcp-js-1.0.40.tgz", + "integrity": "sha512-KKBY8AKudLU8RQOsTY/VQX293UDWVeAgm7LxEkkz5th54l6JdJGrNdM7uFPjSy2k+ojmPu3aYZws1pfLrF/5eg==", "dependencies": { "bindings": "^1.5.0", "debug": "^4.3.4", diff --git a/package.json b/package.json index cb57c85..b558960 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "Endpoints for common operations in ThreeTwo", "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": "moleculer-runner --config dist/moleculer.config.js", + "dev": "./scripts/start.sh dev", + "start": "npm run build && ./scripts/start.sh prod", "cli": "moleculer connect NATS", "ci": "jest --watch", "test": "jest --coverage", @@ -68,7 +68,7 @@ "mkdirp": "^0.5.5", "moleculer-bullmq": "^3.0.0", "moleculer-db": "^0.8.23", - "moleculer-db-adapter-mongoose": "^0.9.2", + "moleculer-db-adapter-mongoose": "^0.9.4", "moleculer-io": "^2.2.0", "moleculer-web": "^0.10.5", "mongoosastic-ts": "^6.0.3", diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100755 index 0000000..f89617e --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Check if the first argument is 'dev', use ts-node; otherwise, use node +MODE=$1 + +# Extract the host and port from MONGO_URI +HOST_PORT=$(echo $MONGO_URI | sed -e 's/mongodb:\/\///' -e 's/\/.*$//') + +# Assuming the script is called from the project root +PROJECT_ROOT=$(pwd) +CONFIG_PATH="$PROJECT_ROOT/moleculer.config.ts" + +# Set the correct path for moleculer-runner based on the mode +if [ "$MODE" == "dev" ]; then + # For development: use ts-node + MOLECULER_RUNNER="ts-node $PROJECT_ROOT/node_modules/moleculer/bin/moleculer-runner.js --hot --repl --config $CONFIG_PATH $PROJECT_ROOT/services/**/*.service.ts" +else + # For production: direct node execution of the compiled JavaScript + MOLECULER_RUNNER="moleculer-runner --config $PROJECT_ROOT/dist/moleculer.config.js $PROJECT_ROOT/dist/services/**/*.service.js" +fi + +# Run wait-for-it, then start the application +./scripts/wait-for-it.sh $HOST_PORT -- $MOLECULER_RUNNER diff --git a/scripts/wait-for-it.sh b/scripts/wait-for-it.sh new file mode 100755 index 0000000..c092856 --- /dev/null +++ b/scripts/wait-for-it.sh @@ -0,0 +1,190 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} +if [[ $OSTYPE == 'darwin'* ]]; then + if ! command -v gtimeout &> /dev/null + then + echo "missing gtimeout (`brew install coreutils`)" + exit + fi + alias timeout=gtimeout +fi + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# Check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) + +WAITFORIT_BUSYTIMEFLAG="" +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + # Check if busybox timeout uses -t flag + # (recent Alpine versions don't support -t anymore) + if timeout &>/dev/stdout | grep -q -e '-t '; then + WAITFORIT_BUSYTIMEFLAG="-t" + fi +else + WAITFORIT_ISBUSY=0 +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi diff --git a/services/library.service.ts b/services/library.service.ts index e417ae1..925a340 100644 --- a/services/library.service.ts +++ b/services/library.service.ts @@ -58,9 +58,11 @@ import klaw from "klaw"; import path from "path"; import { COMICS_DIRECTORY, USERDATA_DIRECTORY } from "../constants/directories"; -console.log(`MONGO -> ${process.env.MONGO_URI}`); -export default class ImportService extends Service { - public constructor(public broker: ServiceBroker) { +export default class LibraryService extends Service { + public constructor( + public broker: ServiceBroker, + schema: ServiceSchema<{}> = { name: "library" } + ) { super(broker); this.parseServiceSchema({ name: "library", -- 2.49.1 From 323548c0ff8d2fbe83f3097098c37610d253c877 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Wed, 15 May 2024 11:32:11 -0500 Subject: [PATCH 03/15] =?UTF-8?q?=F0=9F=94=A7=20WIP=20Dockerfile=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 7 ++++--- config/redis.config.ts | 47 +++++++++++++++++++++++++++++++++++++----- moleculer.config.ts | 2 +- 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4c2f202..5c202e8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,6 +42,8 @@ RUN node -v && npm -v COPY package.json package-lock.json ./ COPY moleculer.config.ts ./ COPY tsconfig.json ./ +COPY scripts ./scripts +RUN chmod +x ./scripts/* # Install application dependencies RUN npm install @@ -50,9 +52,8 @@ RUN npm install -g typescript ts-node # Copy the rest of the application files COPY . . -# Build and clean up -RUN npm run build \ - && npm prune +# clean up +RUN npm prune # Expose the application's port EXPOSE 3000 diff --git a/config/redis.config.ts b/config/redis.config.ts index 3d6cf0b..26a4cb8 100644 --- a/config/redis.config.ts +++ b/config/redis.config.ts @@ -1,10 +1,47 @@ import { createClient } from "redis"; -const redisURL = new URL(process.env.REDIS_URI); +import { URL } from "url"; -const pubClient = createClient({ url: `redis://${redisURL.hostname}:6379` }); -(async () => { - await pubClient.connect(); -})(); +// Ensure that the REDIS_URI environment variable is set +const redisURL = process.env.REDIS_URI ? new URL(process.env.REDIS_URI) : null; +if (!redisURL) { + throw new Error("REDIS_URI environment variable is not set."); +} + +// Function to create a Redis client +const createRedisClient = (url) => { + const client = createClient({ url }); + + client.on("error", (err) => { + console.error("Redis Client Error", err); + }); + + client.on("connect", () => { + console.log("Connected to Redis:", url); + }); + + client.on("reconnecting", () => { + console.log("Reconnecting to Redis..."); + }); + + // Attempt to connect with error handling + client.connect().catch((err) => { + console.error("Failed to connect to Redis:", err); + }); + + return client; +}; + +// Create publisher and subscriber clients +const pubClient = createRedisClient(process.env.REDIS_URI); const subClient = pubClient.duplicate(); +// Ensure subscriber client handles connection and errors +subClient.on("error", (err) => { + console.error("Redis Subscriber Client Error", err); +}); + +subClient.connect().catch((err) => { + console.error("Failed to connect Redis Subscriber:", err); +}); + export { subClient, pubClient }; diff --git a/moleculer.config.ts b/moleculer.config.ts index 69e2ce2..b6e15d1 100644 --- a/moleculer.config.ts +++ b/moleculer.config.ts @@ -90,7 +90,7 @@ const brokerConfig: BrokerOptions = { // More info: https://moleculer.services/docs/0.14/networking.html // Note: During the development, you don't need to define it because all services will be loaded locally. // In production you can set it via `TRANSPORTER=nats://localhost:4222` environment variable. - transporter: process.env.REDIS_URI || "redis://localhost:6379", + transporter: process.env.REDIS_URI || "redis://127.0.0.1:6379", // Define a cacher. // More info: https://moleculer.services/docs/0.14/caching.html -- 2.49.1 From 4680fd0875b737006224a4c51453cd12ab259701 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Wed, 15 May 2024 11:47:08 -0500 Subject: [PATCH 04/15] =?UTF-8?q?=E2=AC=85=EF=B8=8F=20Reverted=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/redis.config.ts | 4 ++-- moleculer.config.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/redis.config.ts b/config/redis.config.ts index 26a4cb8..df743ae 100644 --- a/config/redis.config.ts +++ b/config/redis.config.ts @@ -2,7 +2,7 @@ import { createClient } from "redis"; import { URL } from "url"; // Ensure that the REDIS_URI environment variable is set -const redisURL = process.env.REDIS_URI ? new URL(process.env.REDIS_URI) : null; +const redisURL = process.env.REDIS_URI; if (!redisURL) { throw new Error("REDIS_URI environment variable is not set."); } @@ -10,7 +10,7 @@ if (!redisURL) { // Function to create a Redis client const createRedisClient = (url) => { const client = createClient({ url }); - + console.log(client) client.on("error", (err) => { console.error("Redis Client Error", err); }); diff --git a/moleculer.config.ts b/moleculer.config.ts index b6e15d1..69e2ce2 100644 --- a/moleculer.config.ts +++ b/moleculer.config.ts @@ -90,7 +90,7 @@ const brokerConfig: BrokerOptions = { // More info: https://moleculer.services/docs/0.14/networking.html // Note: During the development, you don't need to define it because all services will be loaded locally. // In production you can set it via `TRANSPORTER=nats://localhost:4222` environment variable. - transporter: process.env.REDIS_URI || "redis://127.0.0.1:6379", + transporter: process.env.REDIS_URI || "redis://localhost:6379", // Define a cacher. // More info: https://moleculer.services/docs/0.14/caching.html -- 2.49.1 From dc9dabca488d35f8013882e6cde6c44a45aa2f31 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Wed, 15 May 2024 12:00:51 -0500 Subject: [PATCH 05/15] =?UTF-8?q?=F0=9F=94=A7=20Fixed=20REDIS=5FURI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/redis.config.ts | 39 ++++++++------------------------------- 1 file changed, 8 insertions(+), 31 deletions(-) diff --git a/config/redis.config.ts b/config/redis.config.ts index df743ae..18dcd27 100644 --- a/config/redis.config.ts +++ b/config/redis.config.ts @@ -1,47 +1,24 @@ import { createClient } from "redis"; -import { URL } from "url"; -// Ensure that the REDIS_URI environment variable is set const redisURL = process.env.REDIS_URI; -if (!redisURL) { - throw new Error("REDIS_URI environment variable is not set."); -} +if (!redisURL) throw new Error("REDIS_URI environment variable is not set."); -// Function to create a Redis client const createRedisClient = (url) => { const client = createClient({ url }); - console.log(client) - client.on("error", (err) => { - console.error("Redis Client Error", err); - }); - client.on("connect", () => { - console.log("Connected to Redis:", url); - }); + client.on("error", (err) => console.error("Redis Client Error", err)); + client.on("connect", () => console.log("Connected to Redis:", url)); + client.on("reconnecting", () => console.log("Reconnecting to Redis...")); - client.on("reconnecting", () => { - console.log("Reconnecting to Redis..."); - }); - - // Attempt to connect with error handling - client.connect().catch((err) => { - console.error("Failed to connect to Redis:", err); - }); + client.connect().catch((err) => console.error("Failed to connect to Redis:", err)); return client; }; -// Create publisher and subscriber clients -const pubClient = createRedisClient(process.env.REDIS_URI); +const pubClient = createRedisClient(redisURL); const subClient = pubClient.duplicate(); -// Ensure subscriber client handles connection and errors -subClient.on("error", (err) => { - console.error("Redis Subscriber Client Error", err); -}); - -subClient.connect().catch((err) => { - console.error("Failed to connect Redis Subscriber:", err); -}); +subClient.on("error", (err) => console.error("Redis Subscriber Client Error", err)); +subClient.connect().catch((err) => console.error("Failed to connect Redis Subscriber:", err)); export { subClient, pubClient }; -- 2.49.1 From a936df314463c2128de77af582677d1d1c95a8c8 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Wed, 15 May 2024 12:13:50 -0500 Subject: [PATCH 06/15] =?UTF-8?q?=F0=9F=AA=B2=20Added=20a=20console.log?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/redis.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/redis.config.ts b/config/redis.config.ts index 18dcd27..f5f89eb 100644 --- a/config/redis.config.ts +++ b/config/redis.config.ts @@ -1,5 +1,5 @@ import { createClient } from "redis"; - +console.log(`REDIS_URI: ${process.env.REDIS_URI}`); const redisURL = process.env.REDIS_URI; if (!redisURL) throw new Error("REDIS_URI environment variable is not set."); -- 2.49.1 From 66f9d63b446a8e9acc6fcaf083bd3c53c1806936 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Wed, 15 May 2024 16:58:49 -0500 Subject: [PATCH 07/15] =?UTF-8?q?=F0=9F=94=A7=20Debuggin=20Redis=20connect?= =?UTF-8?q?ivity=20issue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/redis.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/redis.config.ts b/config/redis.config.ts index f5f89eb..8998da0 100644 --- a/config/redis.config.ts +++ b/config/redis.config.ts @@ -4,7 +4,7 @@ const redisURL = process.env.REDIS_URI; if (!redisURL) throw new Error("REDIS_URI environment variable is not set."); const createRedisClient = (url) => { - const client = createClient({ url }); + const client = createClient(url); client.on("error", (err) => console.error("Redis Client Error", err)); client.on("connect", () => console.log("Connected to Redis:", url)); -- 2.49.1 From 03f6623ed0e0c98cc774c05c7a915d9dd0020a74 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Wed, 15 May 2024 21:27:38 -0500 Subject: [PATCH 08/15] =?UTF-8?q?=F0=9F=94=A7=20Fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/redis.config.ts | 25 ++----- docker-compose.env | 9 ++- docker-compose.yml | 145 +++++++++++++++++++++++++++-------------- 3 files changed, 111 insertions(+), 68 deletions(-) diff --git a/config/redis.config.ts b/config/redis.config.ts index 8998da0..554062b 100644 --- a/config/redis.config.ts +++ b/config/redis.config.ts @@ -1,24 +1,11 @@ import { createClient } from "redis"; -console.log(`REDIS_URI: ${process.env.REDIS_URI}`); -const redisURL = process.env.REDIS_URI; -if (!redisURL) throw new Error("REDIS_URI environment variable is not set."); -const createRedisClient = (url) => { - const client = createClient(url); - - client.on("error", (err) => console.error("Redis Client Error", err)); - client.on("connect", () => console.log("Connected to Redis:", url)); - client.on("reconnecting", () => console.log("Reconnecting to Redis...")); - - client.connect().catch((err) => console.error("Failed to connect to Redis:", err)); - - return client; -}; - -const pubClient = createRedisClient(redisURL); +const pubClient = createClient({ + url: process.env.REDIS_URI || 'redis://localhost:6379' +}); +(async () => { + await pubClient.connect(); +})(); const subClient = pubClient.duplicate(); -subClient.on("error", (err) => console.error("Redis Subscriber Client Error", err)); -subClient.connect().catch((err) => console.error("Failed to connect Redis Subscriber:", err)); - export { subClient, pubClient }; diff --git a/docker-compose.env b/docker-compose.env index 68dcb92..c4ff2a8 100644 --- a/docker-compose.env +++ b/docker-compose.env @@ -3,7 +3,14 @@ LOGGER=true LOGLEVEL=info SERVICEDIR=dist/services -TRANSPORTER=nats://nats:4222 +TRANSPORTER=redis://redis:6379 +COMICS_DIRECTORY=/Users/rishi/work/threetwo-core-service/comics +USERDATA_DIRECTORY=/Users/rishi/work/threetwo-core-service/userdata +REDIS_URI=redis://redis:6379 +ELASTICSEARCH_URI=http://elasticsearch:9200 +MONGO_URI=mongodb://mongo:27017/threetwo +UNRAR_BIN_PATH=/opt/homebrew/bin/unrar +SEVENZ_BINARY_PATH=/opt/homebrew/bin/7za CACHER=Memory diff --git a/docker-compose.yml b/docker-compose.yml index 692eb3a..4f780e7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,58 +1,107 @@ -version: "3.3" +x-userdata-volume: &userdata-volume + type: bind + source: ${USERDATA_DIRECTORY} + target: /userdata + +x-comics-volume: &comics-volume + type: bind + source: ${COMICS_DIRECTORY} + target: /comics services: - - api: + core-services: 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" + context: https://github.com/rishighan/threetwo-core-service.git + image: frishi/threetwo-core-service + container_name: core-services ports: - - 3000:80 - - 3001:8080 + - "3000:3000" + - "3001:3001" + depends_on: + - db + - redis + - elasticsearch + - kafka + - zookeeper + environment: + name: core-services + SERVICES: api,library,jobqueue,settings,search,socket,imagetransformation,torrentjobs,opds + env_file: docker-compose.env volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro + - *comics-volume + - *userdata-volume networks: - - internal - - default + - proxy + + zookeeper: + image: zookeeper:latest + container_name: zookeeper + ports: + - "2181:2181" + networks: + - proxy + + kafka: + image: apache/kafka:latest + container_name: kafka + ports: + - "9092:9092" + environment: + KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka:9093,OUTSIDE://localhost:9092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT + KAFKA_LISTENERS: INSIDE://0.0.0.0:9093,OUTSIDE://0.0.0.0:9092 + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + depends_on: + - zookeeper + networks: + - proxy + + db: + image: "bitnami/mongodb:latest" + container_name: database + networks: + - proxy + ports: + - "27017:27017" + volumes: + - "mongodb_data:/bitnami/mongodb" + + redis: + image: "bitnami/redis:latest" + container_name: redis + environment: + ALLOW_EMPTY_PASSWORD: "yes" + networks: + - proxy + ports: + - "6379:6379" + + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:7.16.2 + container_name: elasticsearch + environment: + - "discovery.type=single-node" + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + - "xpack.security.enabled=true" + - "xpack.security.authc.api_key.enabled=true" + - "ELASTIC_PASSWORD=password" + ulimits: + memlock: + soft: -1 + hard: -1 + ports: + - 9200:9200 + networks: + - proxy networks: - internal: + proxy: + external: true volumes: - data: + mongodb_data: + driver: local + elasticsearch: + driver: local -- 2.49.1 From a06896ffccbd6d7dfbed5508fd1700e770abaeae Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Thu, 16 May 2024 14:03:07 -0400 Subject: [PATCH 09/15] =?UTF-8?q?=F0=9F=94=A7=20Reverting=20to=20nats=20fo?= =?UTF-8?q?r=20transporter=20needs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.env | 2 +- moleculer.config.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.env b/docker-compose.env index c4ff2a8..739cd70 100644 --- a/docker-compose.env +++ b/docker-compose.env @@ -3,7 +3,7 @@ LOGGER=true LOGLEVEL=info SERVICEDIR=dist/services -TRANSPORTER=redis://redis:6379 +TRANSPORTER=nats://localhost:4222 COMICS_DIRECTORY=/Users/rishi/work/threetwo-core-service/comics USERDATA_DIRECTORY=/Users/rishi/work/threetwo-core-service/userdata REDIS_URI=redis://redis:6379 diff --git a/moleculer.config.ts b/moleculer.config.ts index 69e2ce2..6744107 100644 --- a/moleculer.config.ts +++ b/moleculer.config.ts @@ -90,7 +90,7 @@ const brokerConfig: BrokerOptions = { // More info: https://moleculer.services/docs/0.14/networking.html // Note: During the development, you don't need to define it because all services will be loaded locally. // In production you can set it via `TRANSPORTER=nats://localhost:4222` environment variable. - transporter: process.env.REDIS_URI || "redis://localhost:6379", + transporter: "nats://localhost:4222", // Define a cacher. // More info: https://moleculer.services/docs/0.14/caching.html -- 2.49.1 From 8dcb17a6a0293084f912c02c5b05190df606d728 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Thu, 16 May 2024 14:15:09 -0400 Subject: [PATCH 10/15] =?UTF-8?q?=F0=9F=94=A7=20Reverted?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.env | 2 +- moleculer.config.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.env b/docker-compose.env index 739cd70..1197ca2 100644 --- a/docker-compose.env +++ b/docker-compose.env @@ -3,7 +3,7 @@ LOGGER=true LOGLEVEL=info SERVICEDIR=dist/services -TRANSPORTER=nats://localhost:4222 + COMICS_DIRECTORY=/Users/rishi/work/threetwo-core-service/comics USERDATA_DIRECTORY=/Users/rishi/work/threetwo-core-service/userdata REDIS_URI=redis://redis:6379 diff --git a/moleculer.config.ts b/moleculer.config.ts index 6744107..9a0ca78 100644 --- a/moleculer.config.ts +++ b/moleculer.config.ts @@ -90,7 +90,7 @@ const brokerConfig: BrokerOptions = { // More info: https://moleculer.services/docs/0.14/networking.html // Note: During the development, you don't need to define it because all services will be loaded locally. // In production you can set it via `TRANSPORTER=nats://localhost:4222` environment variable. - transporter: "nats://localhost:4222", + transporter: process.env.REDIS_URI, // Define a cacher. // More info: https://moleculer.services/docs/0.14/caching.html -- 2.49.1 From cc772504ae84490a25aa4c24e36ffde4f1878130 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Fri, 17 May 2024 01:26:16 -0400 Subject: [PATCH 11/15] =?UTF-8?q?=F0=9F=94=A7=20Fixed=20the=20Redis=20disc?= =?UTF-8?q?onnection=20issue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/redis.config.ts | 35 +++++++++--- docker-compose.env | 3 +- docker-compose.yml | 8 +-- moleculer.config.ts | 3 +- package-lock.json | 95 +++------------------------------ package.json | 4 +- services/jobqueue.service.ts | 4 +- services/socket.service.ts | 17 +++++- services/torrentjobs.service.ts | 3 +- 9 files changed, 65 insertions(+), 107 deletions(-) diff --git a/config/redis.config.ts b/config/redis.config.ts index 554062b..47b87d9 100644 --- a/config/redis.config.ts +++ b/config/redis.config.ts @@ -1,11 +1,30 @@ -import { createClient } from "redis"; +// Import the Redis library +import Redis from "ioredis"; -const pubClient = createClient({ - url: process.env.REDIS_URI || 'redis://localhost:6379' +// Environment variable for Redis URI +const redisURI = process.env.REDIS_URI || "redis://localhost:6379"; +console.log(`process.env.REDIS_URI is ${process.env.REDIS_URI}`) +// Creating the publisher client +const pubClient = new Redis(redisURI); + +// Creating the subscriber client +const subClient = new Redis(redisURI); + +// Handle connection events for the publisher +pubClient.on("connect", () => { + console.log("Publisher client connected to Redis."); +}); +pubClient.on("error", (err) => { + console.error("Publisher client failed to connect to Redis:", err); }); -(async () => { - await pubClient.connect(); -})(); -const subClient = pubClient.duplicate(); -export { subClient, pubClient }; +// Handle connection events for the subscriber +subClient.on("connect", () => { + console.log("Subscriber client connected to Redis."); +}); +subClient.on("error", (err) => { + console.error("Subscriber client failed to connect to Redis:", err); +}); + +// Export the clients for use in other parts of the application +export { pubClient, subClient }; diff --git a/docker-compose.env b/docker-compose.env index 1197ca2..f70a873 100644 --- a/docker-compose.env +++ b/docker-compose.env @@ -3,12 +3,11 @@ LOGGER=true LOGLEVEL=info SERVICEDIR=dist/services - COMICS_DIRECTORY=/Users/rishi/work/threetwo-core-service/comics USERDATA_DIRECTORY=/Users/rishi/work/threetwo-core-service/userdata REDIS_URI=redis://redis:6379 ELASTICSEARCH_URI=http://elasticsearch:9200 -MONGO_URI=mongodb://mongo:27017/threetwo +MONGO_URI=mongodb://db:27017/threetwo UNRAR_BIN_PATH=/opt/homebrew/bin/unrar SEVENZ_BINARY_PATH=/opt/homebrew/bin/7za diff --git a/docker-compose.yml b/docker-compose.yml index 4f780e7..a521b4a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,9 @@ x-comics-volume: &comics-volume services: core-services: build: - context: https://github.com/rishighan/threetwo-core-service.git + # context: https://github.com/rishighan/threetwo-core-service.git + context: ./ + dockerfile: Dockerfile image: frishi/threetwo-core-service container_name: core-services ports: @@ -25,7 +27,7 @@ services: - zookeeper environment: name: core-services - SERVICES: api,library,jobqueue,settings,search,socket,imagetransformation,torrentjobs,opds + SERVICES: api,library,imagetransformation,opds,search,settings,jobqueue,socket,torrentjobs env_file: docker-compose.env volumes: - *comics-volume @@ -59,7 +61,7 @@ services: - proxy db: - image: "bitnami/mongodb:latest" + image: "mongo:latest" container_name: database networks: - proxy diff --git a/moleculer.config.ts b/moleculer.config.ts index 9a0ca78..d0a0b79 100644 --- a/moleculer.config.ts +++ b/moleculer.config.ts @@ -5,6 +5,7 @@ import { MetricRegistry, ServiceBroker, } from "moleculer"; +const RedisTransporter = require("moleculer").Transporters.Redis; /** * Moleculer ServiceBroker configuration file @@ -90,7 +91,7 @@ const brokerConfig: BrokerOptions = { // More info: https://moleculer.services/docs/0.14/networking.html // Note: During the development, you don't need to define it because all services will be loaded locally. // In production you can set it via `TRANSPORTER=nats://localhost:4222` environment variable. - transporter: process.env.REDIS_URI, + transporter: new RedisTransporter(process.env.REDIS_URI), // Define a cacher. // More info: https://moleculer.services/docs/0.14/caching.html diff --git a/package-lock.json b/package-lock.json index d978e66..77abcd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "http-response-stream": "^1.0.9", "image-js": "^0.34.0", "imghash": "^0.0.9", + "ioredis": "^5.4.1", "jsdom": "^21.1.0", "klaw": "^4.1.0", "leven": "^3.1.0", @@ -40,14 +41,13 @@ "moleculer-db": "^0.8.23", "moleculer-db-adapter-mongoose": "^0.9.4", "moleculer-io": "^2.2.0", - "moleculer-web": "^0.10.5", + "moleculer-web": "^0.10.7", "mongoosastic-ts": "^6.0.3", "mongoose": "^6.10.4", "mongoose-paginate-v2": "^1.3.18", "nats": "^1.3.2", "opds-extra": "^3.0.10", "p7zip-threetwo": "^1.0.4", - "redis": "^4.6.5", "sanitize-filename-ts": "^1.0.2", "sharp": "^0.33.3", "threetwo-ui-typings": "^1.0.14", @@ -2656,64 +2656,6 @@ "resolved": "https://registry.npmjs.org/@npcz/magic/-/magic-1.3.14.tgz", "integrity": "sha512-Jt+fjEVAVoDJh9N+nrQ/IQSC6MFLpIDag8VXxvdVGGG5mrGK2HH4X5KqC9zgzb20fqk2vBM9g2QzyczylKVvqg==" }, - "node_modules/@redis/bloom": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", - "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/client": { - "version": "1.5.9", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.9.tgz", - "integrity": "sha512-SffgN+P1zdWJWSXBvJeynvEnmnZrYmtKSRW00xl8pOPFOMJjxRR9u0frSxJpPR6Y4V+k54blJjGW7FgxbTI7bQ==", - "dependencies": { - "cluster-key-slot": "1.1.2", - "generic-pool": "3.9.0", - "yallist": "4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@redis/client/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/@redis/graph": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz", - "integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/json": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz", - "integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/search": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.3.tgz", - "integrity": "sha512-4Dg1JjvCevdiCBTZqjhKkGoC5/BcB7k9j99kdMnaXFXg8x4eyOIVg9487CMv7/BUVkFLZCaIh8ead9mU15DNng==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/time-series": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.5.tgz", - "integrity": "sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==", - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, "node_modules/@root/walk": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@root/walk/-/walk-1.1.0.tgz", @@ -7160,14 +7102,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/generic-pool": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", - "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", - "engines": { - "node": ">= 4" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -7806,9 +7740,9 @@ "integrity": "sha512-kO3CjNfLZ9t+tHxAMd+Xk4v3D/31E91rMs1dHrm7ikEQrlZ8mLDbQ4z3tZfDM48zOkReas2jx8MWSAmN9+c8Fw==" }, "node_modules/ioredis": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz", - "integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz", + "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==", "dependencies": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", @@ -10232,9 +10166,9 @@ } }, "node_modules/moleculer-web": { - "version": "0.10.6", - "resolved": "https://registry.npmjs.org/moleculer-web/-/moleculer-web-0.10.6.tgz", - "integrity": "sha512-MGNIH6mXLU2Wj63bAgoVzdhMKXALp99F5UHuiBgS2ywakdWEUl/q7GlMblvscioCCkXuUWezId85J0yioYxedg==", + "version": "0.10.7", + "resolved": "https://registry.npmjs.org/moleculer-web/-/moleculer-web-0.10.7.tgz", + "integrity": "sha512-/UJtV+O7iQ3aSg/xi/sw3ZswhvzkigzGPjKOR5R97sm2FSihKuLTftUpXlk4dYls7/8c8WSz6H/M/40BenEx9Q==", "dependencies": { "@fastify/busboy": "^1.0.0", "body-parser": "^1.19.0", @@ -14142,19 +14076,6 @@ "recursive-watch": "bin.js" } }, - "node_modules/redis": { - "version": "4.6.8", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.8.tgz", - "integrity": "sha512-S7qNkPUYrsofQ0ztWlTHSaK0Qqfl1y+WMIxrzeAGNG+9iUZB4HGeBgkHxE6uJJ6iXrkvLd1RVJ2nvu6H1sAzfQ==", - "dependencies": { - "@redis/bloom": "1.2.0", - "@redis/client": "1.5.9", - "@redis/graph": "1.1.0", - "@redis/json": "1.0.4", - "@redis/search": "1.1.3", - "@redis/time-series": "1.0.5" - } - }, "node_modules/redis-errors": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", diff --git a/package.json b/package.json index b558960..1620123 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "http-response-stream": "^1.0.9", "image-js": "^0.34.0", "imghash": "^0.0.9", + "ioredis": "^5.4.1", "jsdom": "^21.1.0", "klaw": "^4.1.0", "leven": "^3.1.0", @@ -70,14 +71,13 @@ "moleculer-db": "^0.8.23", "moleculer-db-adapter-mongoose": "^0.9.4", "moleculer-io": "^2.2.0", - "moleculer-web": "^0.10.5", + "moleculer-web": "^0.10.7", "mongoosastic-ts": "^6.0.3", "mongoose": "^6.10.4", "mongoose-paginate-v2": "^1.3.18", "nats": "^1.3.2", "opds-extra": "^3.0.10", "p7zip-threetwo": "^1.0.4", - "redis": "^4.6.5", "sanitize-filename-ts": "^1.0.2", "sharp": "^0.33.3", "threetwo-ui-typings": "^1.0.14", diff --git a/services/jobqueue.service.ts b/services/jobqueue.service.ts index 830e18c..1c323ad 100644 --- a/services/jobqueue.service.ts +++ b/services/jobqueue.service.ts @@ -11,10 +11,10 @@ import { } from "../utils/uncompression.utils"; import { isNil, isUndefined } from "lodash"; import { pubClient } from "../config/redis.config"; +import IORedis from 'ioredis'; import path from "path"; const { MoleculerError } = require("moleculer").Errors; -console.log(process.env.REDIS_URI); export default class JobQueueService extends Service { public constructor(public broker: ServiceBroker) { super(broker); @@ -24,7 +24,7 @@ export default class JobQueueService extends Service { mixins: [DbMixin("comics", Comic), BullMqMixin], settings: { bullmq: { - client: process.env.REDIS_URI, + client: new IORedis(process.env.REDIS_URI, { maxRetriesPerRequest: null }), }, }, actions: { diff --git a/services/socket.service.ts b/services/socket.service.ts index 6871b11..3e3a4f7 100644 --- a/services/socket.service.ts +++ b/services/socket.service.ts @@ -1,7 +1,6 @@ "use strict"; import { Service, ServiceBroker, ServiceSchema, Context } from "moleculer"; import { JobType } from "moleculer-bullmq"; -import { createClient } from "redis"; import { createAdapter } from "@socket.io/redis-adapter"; import Session from "../models/session.model"; import { pubClient, subClient } from "../config/redis.config"; @@ -325,6 +324,22 @@ export default class SocketService extends Service { }, }, async started() { + this.logger.info("Starting Socket Service..."); + this.logger.debug("pubClient:", pubClient); + this.logger.debug("subClient:", subClient); + if (!pubClient || !subClient) { + this.logger.error("Redis clients are not initialized!"); + throw new Error("Redis clients are not initialized!"); + } + + // Additional checks or logic if necessary + if (pubClient.status !== "ready") { + await pubClient.connect(); + } + + if (subClient.status !== "ready") { + await subClient.connect(); + } this.io.on("connection", async (socket) => { console.log( `socket.io server connected to client with session ID: ${socket.id}` diff --git a/services/torrentjobs.service.ts b/services/torrentjobs.service.ts index c9cdf73..0802206 100644 --- a/services/torrentjobs.service.ts +++ b/services/torrentjobs.service.ts @@ -10,6 +10,7 @@ import { DbMixin } from "../mixins/db.mixin"; import Comic from "../models/comic.model"; import BullMqMixin from "moleculer-bullmq"; const { MoleculerError } = require("moleculer").Errors; +import IORedis from 'ioredis'; export default class ImageTransformation extends Service { // @ts-ignore @@ -23,7 +24,7 @@ export default class ImageTransformation extends Service { mixins: [DbMixin("comics", Comic), BullMqMixin], settings: { bullmq: { - client: process.env.REDIS_URI, + client: new IORedis(process.env.REDIS_URI), }, }, hooks: {}, -- 2.49.1 From cc271021e0113fff0199d3aec5355d3e28df0843 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Sun, 19 May 2024 21:19:15 -0400 Subject: [PATCH 12/15] =?UTF-8?q?=F0=9F=90=B3=20Created=20a=20deps=20docke?= =?UTF-8?q?r-compose=20stack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dependencies.docker-compose.yml | 86 +++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 dependencies.docker-compose.yml diff --git a/dependencies.docker-compose.yml b/dependencies.docker-compose.yml new file mode 100644 index 0000000..d2a3adb --- /dev/null +++ b/dependencies.docker-compose.yml @@ -0,0 +1,86 @@ +version: "3.7" + +services: + zookeeper: + image: bitnami/zookeeper:latest + container_name: zookeeper + ports: + - "2181:2181" + environment: + - ALLOW_ANONYMOUS_LOGIN=yes + - ZOO_MAX_CLIENT_CNXNS=100 + - ZOO_MAX_SESSION_TIMEOUT=60000 + - ZOO_INIT_LIMIT=10 + - ZOO_SYNC_LIMIT=5 + - ZOO_MAX_BUFFER=10485760 # Increased buffer size + networks: + - proxy + + kafka: + image: bitnami/kafka:latest + container_name: kafka + ports: + - "9092:9092" + environment: + KAFKA_BROKER_ID: 1 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 + KAFKA_LISTENERS: PLAINTEXT://:9092 + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + ALLOW_PLAINTEXT_LISTENER: "yes" + KAFKA_MAX_REQUEST_SIZE: 10485760 # Increased request size + KAFKA_MESSAGE_MAX_BYTES: 10485760 # Increased message size + KAFKA_REPLICA_FETCH_MAX_BYTES: 10485760 # Increased fetch size + volumes: + - /var/run/docker.sock:/var/run/docker.sock + depends_on: + - zookeeper + networks: + - proxy + + db: + image: "mongo:latest" + container_name: database + networks: + - proxy + ports: + - "27017:27017" + volumes: + - "mongodb_data:/bitnami/mongodb" + + redis: + image: "bitnami/redis:latest" + container_name: queue + environment: + ALLOW_EMPTY_PASSWORD: "yes" + networks: + - proxy + ports: + - "6379:6379" + + elasticsearch: + image: docker.elastic.co/elasticsearch/elasticsearch:7.16.2 + container_name: elasticsearch + environment: + - "discovery.type=single-node" + - "ES_JAVA_OPTS=-Xms512m -Xmx512m" + - "xpack.security.enabled=true" + - "xpack.security.authc.api_key.enabled=true" + - "ELASTIC_PASSWORD=password" + ulimits: + memlock: + soft: -1 + hard: -1 + ports: + - "9200:9200" + networks: + - proxy + +networks: + proxy: + external: true + +volumes: + mongodb_data: + driver: local + elasticsearch: + driver: local -- 2.49.1 From e01421f17b1f95c0b4d5ca143c49f7628e9fadd4 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Thu, 23 May 2024 23:15:03 -0400 Subject: [PATCH 13/15] =?UTF-8?q?=F0=9F=90=B3=20Added=20all=20other=20deps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dependencies.docker-compose.yml | 64 +++++++++++++++++---------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/dependencies.docker-compose.yml b/dependencies.docker-compose.yml index d2a3adb..5a6cf04 100644 --- a/dependencies.docker-compose.yml +++ b/dependencies.docker-compose.yml @@ -1,47 +1,49 @@ -version: "3.7" - services: - zookeeper: - image: bitnami/zookeeper:latest - container_name: zookeeper + zoo1: + image: confluentinc/cp-zookeeper:7.3.2 + hostname: zoo1 + container_name: zoo1 ports: - "2181:2181" environment: - - ALLOW_ANONYMOUS_LOGIN=yes - - ZOO_MAX_CLIENT_CNXNS=100 - - ZOO_MAX_SESSION_TIMEOUT=60000 - - ZOO_INIT_LIMIT=10 - - ZOO_SYNC_LIMIT=5 - - ZOO_MAX_BUFFER=10485760 # Increased buffer size + ZOOKEEPER_CLIENT_PORT: 2181 + ZOOKEEPER_SERVER_ID: 1 + ZOOKEEPER_SERVERS: zoo1:2888:3888 networks: - - proxy + - kafka-net - kafka: - image: bitnami/kafka:latest - container_name: kafka + kafka1: + image: confluentinc/cp-kafka:7.3.2 + hostname: kafka1 + container_name: kafka1 ports: - "9092:9092" + - "29092:29092" + - "9999:9999" environment: + KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka1:19092,EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9092,DOCKER://host.docker.internal:29092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,DOCKER:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL + KAFKA_ZOOKEEPER_CONNECT: "zoo1:2181" KAFKA_BROKER_ID: 1 - KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 - KAFKA_LISTENERS: PLAINTEXT://:9092 - KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 - ALLOW_PLAINTEXT_LISTENER: "yes" - KAFKA_MAX_REQUEST_SIZE: 10485760 # Increased request size - KAFKA_MESSAGE_MAX_BYTES: 10485760 # Increased message size - KAFKA_REPLICA_FETCH_MAX_BYTES: 10485760 # Increased fetch size - volumes: - - /var/run/docker.sock:/var/run/docker.sock + KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO" + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 + KAFKA_JMX_PORT: 9999 + KAFKA_JMX_HOSTNAME: ${DOCKER_HOST_IP:-127.0.0.1} + KAFKA_AUTHORIZER_CLASS_NAME: kafka.security.authorizer.AclAuthorizer + KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true" depends_on: - - zookeeper + - zoo1 networks: - - proxy + - kafka-net db: image: "mongo:latest" container_name: database networks: - - proxy + - kafka-net ports: - "27017:27017" volumes: @@ -53,7 +55,7 @@ services: environment: ALLOW_EMPTY_PASSWORD: "yes" networks: - - proxy + - kafka-net ports: - "6379:6379" @@ -73,11 +75,11 @@ services: ports: - "9200:9200" networks: - - proxy + - kafka-net networks: - proxy: - external: true + kafka-net: + driver: bridge volumes: mongodb_data: -- 2.49.1 From e61ecb114370013c731928ed5fa444d7ace3c2a9 Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Fri, 24 May 2024 14:22:59 -0400 Subject: [PATCH 14/15] =?UTF-8?q?=F0=9F=94=A7=20Refactor=20for=20docker-co?= =?UTF-8?q?mpose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/redis.config.ts | 8 +- docker-compose.env | 1 + docker-compose.yml | 48 ++++++--- package-lock.json | 186 +++++++++++++++++++++++--------- package.json | 3 +- scripts/start.sh | 8 +- services/jobqueue.service.ts | 34 +++--- services/library.service.ts | 142 ++++++++++++------------ services/torrentjobs.service.ts | 3 +- 9 files changed, 270 insertions(+), 163 deletions(-) diff --git a/config/redis.config.ts b/config/redis.config.ts index 47b87d9..be50db6 100644 --- a/config/redis.config.ts +++ b/config/redis.config.ts @@ -1,14 +1,14 @@ // Import the Redis library -import Redis from "ioredis"; +import IORedis from "ioredis"; // Environment variable for Redis URI const redisURI = process.env.REDIS_URI || "redis://localhost:6379"; -console.log(`process.env.REDIS_URI is ${process.env.REDIS_URI}`) +console.log(`process.env.REDIS_URI is ${process.env.REDIS_URI}`); // Creating the publisher client -const pubClient = new Redis(redisURI); +const pubClient = new IORedis(redisURI); // Creating the subscriber client -const subClient = new Redis(redisURI); +const subClient = new IORedis(redisURI); // Handle connection events for the publisher pubClient.on("connect", () => { diff --git a/docker-compose.env b/docker-compose.env index f70a873..6acb2c4 100644 --- a/docker-compose.env +++ b/docker-compose.env @@ -6,6 +6,7 @@ SERVICEDIR=dist/services COMICS_DIRECTORY=/Users/rishi/work/threetwo-core-service/comics USERDATA_DIRECTORY=/Users/rishi/work/threetwo-core-service/userdata REDIS_URI=redis://redis:6379 +KAFKA_BROKER=kafka1:9092 ELASTICSEARCH_URI=http://elasticsearch:9200 MONGO_URI=mongodb://db:27017/threetwo UNRAR_BIN_PATH=/opt/homebrew/bin/unrar diff --git a/docker-compose.yml b/docker-compose.yml index a521b4a..d93a7a0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,8 +23,8 @@ services: - db - redis - elasticsearch - - kafka - - zookeeper + - kafka1 + - zoo1 environment: name: core-services SERVICES: api,library,imagetransformation,opds,search,settings,jobqueue,socket,torrentjobs @@ -35,28 +35,43 @@ services: networks: - proxy - zookeeper: - image: zookeeper:latest - container_name: zookeeper + zoo1: + image: confluentinc/cp-zookeeper:7.3.2 + hostname: zoo1 + container_name: zoo1 ports: - "2181:2181" + environment: + ZOOKEEPER_CLIENT_PORT: 2181 + ZOOKEEPER_SERVER_ID: 1 + ZOOKEEPER_SERVERS: zoo1:2888:3888 networks: - proxy - kafka: - image: apache/kafka:latest - container_name: kafka + kafka1: + image: confluentinc/cp-kafka:7.3.2 + hostname: kafka1 + container_name: kafka1 ports: - "9092:9092" + - "29092:29092" + - "9999:9999" environment: - KAFKA_ADVERTISED_LISTENERS: INSIDE://kafka:9093,OUTSIDE://localhost:9092 - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT - KAFKA_LISTENERS: INSIDE://0.0.0.0:9093,OUTSIDE://0.0.0.0:9092 - KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 - volumes: - - /var/run/docker.sock:/var/run/docker.sock + KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka1:19092,EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1} :9092,DOCKER://host.docker.internal:29092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,DOCKER:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL + KAFKA_ZOOKEEPER_CONNECT: "zoo1:2181" + KAFKA_BROKER_ID: 1 + KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state. change.logger=INFO" + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 + KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 + KAFKA_JMX_PORT: 9999 + KAFKA_JMX_HOSTNAME: ${DOCKER_HOST_IP:-127.0.0.1} + KAFKA_AUTHORIZER_CLASS_NAME: kafka.security.authorizer.AclAuthorizer + KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true" depends_on: - - zookeeper + - zoo1 networks: - proxy @@ -72,7 +87,8 @@ services: redis: image: "bitnami/redis:latest" - container_name: redis + container_name: redis + hostname: redis environment: ALLOW_EMPTY_PASSWORD: "yes" networks: diff --git a/package-lock.json b/package-lock.json index 77abcd6..9fb4206 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,6 @@ "http-response-stream": "^1.0.9", "image-js": "^0.34.0", "imghash": "^0.0.9", - "ioredis": "^5.4.1", "jsdom": "^21.1.0", "klaw": "^4.1.0", "leven": "^3.1.0", @@ -48,6 +47,7 @@ "nats": "^1.3.2", "opds-extra": "^3.0.10", "p7zip-threetwo": "^1.0.4", + "redis": "^4.6.14", "sanitize-filename-ts": "^1.0.2", "sharp": "^0.33.3", "threetwo-ui-typings": "^1.0.14", @@ -63,6 +63,7 @@ "eslint-plugin-import": "^2.20.2", "eslint-plugin-prefer-arrow": "^1.2.2", "install": "^0.13.0", + "ioredis": "^5.4.1", "jest": "^29.5.0", "jest-cli": "^29.5.0", "moleculer-repl": "^0.7.0", @@ -2656,6 +2657,64 @@ "resolved": "https://registry.npmjs.org/@npcz/magic/-/magic-1.3.14.tgz", "integrity": "sha512-Jt+fjEVAVoDJh9N+nrQ/IQSC6MFLpIDag8VXxvdVGGG5mrGK2HH4X5KqC9zgzb20fqk2vBM9g2QzyczylKVvqg==" }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.5.16", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.16.tgz", + "integrity": "sha512-X1a3xQ5kEMvTib5fBrHKh6Y+pXbeKXqziYuxOUo1ojQNECg4M5Etd1qqyhMap+lFUOAh8S7UYevgJHOm4A+NOg==", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.6.tgz", + "integrity": "sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.6.tgz", + "integrity": "sha512-mZXCxbTYKBQ3M2lZnEddwEAks0Kc7nauire8q20oA0oA/LoA+E/b5Y5KZn232ztPb1FkIGqo12vh3Lf+Vw5iTw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.5.tgz", + "integrity": "sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, "node_modules/@root/walk": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@root/walk/-/walk-1.1.0.tgz", @@ -4623,58 +4682,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bullmq": { - "version": "3.15.8", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-3.15.8.tgz", - "integrity": "sha512-k3uimHGhl5svqD7SEak+iI6c5DxeLOaOXzCufI9Ic0ST3nJr69v71TGR4cXCTXdgCff3tLec5HgoBnfyWjgn5A==", - "dependencies": { - "cron-parser": "^4.6.0", - "glob": "^8.0.3", - "ioredis": "^5.3.2", - "lodash": "^4.17.21", - "msgpackr": "^1.6.2", - "semver": "^7.3.7", - "tslib": "^2.0.0", - "uuid": "^9.0.0" - } - }, - "node_modules/bullmq/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/bullmq/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/bullmq/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -7102,6 +7109,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "engines": { + "node": ">= 4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -10094,6 +10109,58 @@ "node": ">= 10.x.x" } }, + "node_modules/moleculer-bullmq/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/moleculer-bullmq/node_modules/bullmq": { + "version": "3.15.8", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-3.15.8.tgz", + "integrity": "sha512-k3uimHGhl5svqD7SEak+iI6c5DxeLOaOXzCufI9Ic0ST3nJr69v71TGR4cXCTXdgCff3tLec5HgoBnfyWjgn5A==", + "dependencies": { + "cron-parser": "^4.6.0", + "glob": "^8.0.3", + "ioredis": "^5.3.2", + "lodash": "^4.17.21", + "msgpackr": "^1.6.2", + "semver": "^7.3.7", + "tslib": "^2.0.0", + "uuid": "^9.0.0" + } + }, + "node_modules/moleculer-bullmq/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/moleculer-bullmq/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/moleculer-db": { "version": "0.8.24", "resolved": "https://registry.npmjs.org/moleculer-db/-/moleculer-db-0.8.24.tgz", @@ -14076,6 +14143,19 @@ "recursive-watch": "bin.js" } }, + "node_modules/redis": { + "version": "4.6.14", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.14.tgz", + "integrity": "sha512-GrNg/e33HtsQwNXL7kJT+iNFPSwE1IPmd7wzV3j4f2z0EYxZfZE7FVTmUysgAtqQQtg5NXF5SNLR9OdO/UHOfw==", + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.5.16", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.6", + "@redis/search": "1.1.6", + "@redis/time-series": "1.0.5" + } + }, "node_modules/redis-errors": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", diff --git a/package.json b/package.json index 1620123..e4de15a 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "eslint-plugin-import": "^2.20.2", "eslint-plugin-prefer-arrow": "^1.2.2", "install": "^0.13.0", + "ioredis": "^5.4.1", "jest": "^29.5.0", "jest-cli": "^29.5.0", "moleculer-repl": "^0.7.0", @@ -61,7 +62,6 @@ "http-response-stream": "^1.0.9", "image-js": "^0.34.0", "imghash": "^0.0.9", - "ioredis": "^5.4.1", "jsdom": "^21.1.0", "klaw": "^4.1.0", "leven": "^3.1.0", @@ -78,6 +78,7 @@ "nats": "^1.3.2", "opds-extra": "^3.0.10", "p7zip-threetwo": "^1.0.4", + "redis": "^4.6.14", "sanitize-filename-ts": "^1.0.2", "sharp": "^0.33.3", "threetwo-ui-typings": "^1.0.14", diff --git a/scripts/start.sh b/scripts/start.sh index f89617e..f35cb9b 100755 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -1,21 +1,25 @@ #!/bin/bash -# Check if the first argument is 'dev', use ts-node; otherwise, use node -MODE=$1 +echo "Starting script with mode: $MODE" # Extract the host and port from MONGO_URI HOST_PORT=$(echo $MONGO_URI | sed -e 's/mongodb:\/\///' -e 's/\/.*$//') # Assuming the script is called from the project root PROJECT_ROOT=$(pwd) +echo "Project root: $PROJECT_ROOT" + CONFIG_PATH="$PROJECT_ROOT/moleculer.config.ts" +echo "Configuration path: $CONFIG_PATH" # Set the correct path for moleculer-runner based on the mode if [ "$MODE" == "dev" ]; then # For development: use ts-node MOLECULER_RUNNER="ts-node $PROJECT_ROOT/node_modules/moleculer/bin/moleculer-runner.js --hot --repl --config $CONFIG_PATH $PROJECT_ROOT/services/**/*.service.ts" + echo "Moleculer Runner for dev: $MOLECULER_RUNNER" else # For production: direct node execution of the compiled JavaScript MOLECULER_RUNNER="moleculer-runner --config $PROJECT_ROOT/dist/moleculer.config.js $PROJECT_ROOT/dist/services/**/*.service.js" + echo "Moleculer Runner for prod: $MOLECULER_RUNNER" fi # Run wait-for-it, then start the application diff --git a/services/jobqueue.service.ts b/services/jobqueue.service.ts index 1c323ad..d644563 100644 --- a/services/jobqueue.service.ts +++ b/services/jobqueue.service.ts @@ -11,7 +11,6 @@ import { } from "../utils/uncompression.utils"; import { isNil, isUndefined } from "lodash"; import { pubClient } from "../config/redis.config"; -import IORedis from 'ioredis'; import path from "path"; const { MoleculerError } = require("moleculer").Errors; @@ -22,9 +21,10 @@ export default class JobQueueService extends Service { name: "jobqueue", hooks: {}, mixins: [DbMixin("comics", Comic), BullMqMixin], + settings: { bullmq: { - client: new IORedis(process.env.REDIS_URI, { maxRetriesPerRequest: null }), + client: pubClient, }, }, actions: { @@ -57,20 +57,24 @@ export default class JobQueueService extends Service { handler: async ( ctx: Context<{ action: string; description: string }> ) => { - const { action, description } = ctx.params; - // Enqueue the job - const job = await this.localQueue( - ctx, - action, - ctx.params, - { - priority: 10, - } - ); - console.log(`Job ${job.id} enqueued`); - console.log(`${description}`); + try { + const { action, description } = ctx.params; + // Enqueue the job + const job = await this.localQueue( + ctx, + action, + {}, + { + priority: 10, + } + ); + console.log(`Job ${job.id} enqueued`); + console.log(`${description}`); - return job.id; + return job.id; + } catch (error) { + console.error("Failed to enqueue job:", error); + } }, }, diff --git a/services/library.service.ts b/services/library.service.ts index 925a340..efdd91a 100644 --- a/services/library.service.ts +++ b/services/library.service.ts @@ -165,78 +165,52 @@ export default class LibraryService extends Service { }, newImport: { rest: "POST /newImport", - // params: {}, - async handler( - ctx: Context<{ - extractionOptions?: any; - sessionId: string; - }> - ) { + async handler(ctx) { + const { sessionId } = ctx.params; try { - // Get params to be passed to the import jobs - const { sessionId } = ctx.params; - // 1. Walk the Source folder - klaw(path.resolve(COMICS_DIRECTORY)) - // 1.1 Filter on .cb* extensions - .pipe( - through2.obj(function (item, enc, next) { - let fileExtension = path.extname( - item.path - ); - if ( - [".cbz", ".cbr", ".cb7"].includes( - fileExtension - ) - ) { - this.push(item); - } - next(); - }) - ) - // 1.2 Pipe filtered results to the next step - // Enqueue the job in the queue - .on("data", async (item) => { - console.info( - "Found a file at path: %s", - item.path - ); - let comicExists = await Comic.exists({ - "rawFileDetails.name": `${path.basename( - item.path, - path.extname(item.path) - )}`, - }); - if (!comicExists) { - // 2.1 Reset the job counters in Redis - await pubClient.set( - "completedJobCount", - 0 - ); - await pubClient.set( - "failedJobCount", - 0 - ); - // 2.2 Send the extraction job to the queue - this.broker.call("jobqueue.enqueue", { - fileObject: { - filePath: item.path, - fileSize: item.stats.size, - }, - sessionId, - importType: "new", - action: "enqueue.async", - }); - } else { - console.log( - "Comic already exists in the library." - ); - } - }) - .on("end", () => { - console.log("All files traversed."); + // Initialize Redis counters once at the start of the import + await pubClient.set("completedJobCount", 0); + await pubClient.set("failedJobCount", 0); + + // Convert klaw to use a promise-based approach for better flow control + const files = await this.getComicFiles( + COMICS_DIRECTORY + ); + for (const file of files) { + console.info( + "Found a file at path:", + file.path + ); + const comicExists = await Comic.exists({ + "rawFileDetails.name": path.basename( + file.path, + path.extname(file.path) + ), }); + + if (!comicExists) { + // Send the extraction job to the queue + await this.broker.call("jobqueue.enqueue", { + fileObject: { + filePath: file.path, + fileSize: file.stats.size, + }, + sessionId, + importType: "new", + action: "enqueue.async", + }); + } else { + console.log( + "Comic already exists in the library." + ); + } + } + console.log("All files traversed."); } catch (error) { - console.log(error); + console.error( + "Error during newImport processing:", + error + ); } }, }, @@ -821,7 +795,35 @@ export default class LibraryService extends Service { }, }, }, - methods: {}, + methods: { + // Method to walk the directory and filter comic files + getComicFiles: (directory) => { + return new Promise((resolve, reject) => { + const files = []; + klaw(directory) + .pipe( + through2.obj(function (item, enc, next) { + const fileExtension = path.extname( + item.path + ); + if ( + [".cbz", ".cbr", ".cb7"].includes( + fileExtension + ) + ) { + this.push(item); + } + next(); + }) + ) + .on("data", (item) => { + files.push(item); + }) + .on("end", () => resolve(files)) + .on("error", (err) => reject(err)); + }); + }, + }, }); } } diff --git a/services/torrentjobs.service.ts b/services/torrentjobs.service.ts index 0802206..c9cdf73 100644 --- a/services/torrentjobs.service.ts +++ b/services/torrentjobs.service.ts @@ -10,7 +10,6 @@ import { DbMixin } from "../mixins/db.mixin"; import Comic from "../models/comic.model"; import BullMqMixin from "moleculer-bullmq"; const { MoleculerError } = require("moleculer").Errors; -import IORedis from 'ioredis'; export default class ImageTransformation extends Service { // @ts-ignore @@ -24,7 +23,7 @@ export default class ImageTransformation extends Service { mixins: [DbMixin("comics", Comic), BullMqMixin], settings: { bullmq: { - client: new IORedis(process.env.REDIS_URI), + client: process.env.REDIS_URI, }, }, hooks: {}, -- 2.49.1 From 2247411ac8a75f6e8d55f78f7b9406b3db283e7e Mon Sep 17 00:00:00 2001 From: Rishi Ghan Date: Tue, 28 May 2024 08:40:33 -0400 Subject: [PATCH 15/15] =?UTF-8?q?=F0=9F=AA=B3=20Added=20kafka=20to=20the?= =?UTF-8?q?=20docker-compose=20deps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dependencies.docker-compose.yml | 15 +++++++++++++++ docker-compose.env | 1 + services/torrentjobs.service.ts | 3 ++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/dependencies.docker-compose.yml b/dependencies.docker-compose.yml index 5a6cf04..d709dd4 100644 --- a/dependencies.docker-compose.yml +++ b/dependencies.docker-compose.yml @@ -39,6 +39,21 @@ services: networks: - kafka-net + kafka-ui: + container_name: kafka-ui + image: provectuslabs/kafka-ui:latest + ports: + - 8087:8080 + environment: + DYNAMIC_CONFIG_ENABLED: true + volumes: + - /Users/rishi/work/config/kafka-ui/config.yml:/etc/kafkaui/dynamic_config.yaml + depends_on: + - kafka1 + - zoo1 + networks: + - kafka-net + db: image: "mongo:latest" container_name: database diff --git a/docker-compose.env b/docker-compose.env index 6acb2c4..6c801ff 100644 --- a/docker-compose.env +++ b/docker-compose.env @@ -3,6 +3,7 @@ LOGGER=true LOGLEVEL=info SERVICEDIR=dist/services +VITE_UNDERLYING_HOST=localhost COMICS_DIRECTORY=/Users/rishi/work/threetwo-core-service/comics USERDATA_DIRECTORY=/Users/rishi/work/threetwo-core-service/userdata REDIS_URI=redis://redis:6379 diff --git a/services/torrentjobs.service.ts b/services/torrentjobs.service.ts index c9cdf73..737ed3f 100644 --- a/services/torrentjobs.service.ts +++ b/services/torrentjobs.service.ts @@ -9,6 +9,7 @@ import { import { DbMixin } from "../mixins/db.mixin"; import Comic from "../models/comic.model"; import BullMqMixin from "moleculer-bullmq"; +import { pubClient } from "../config/redis.config"; const { MoleculerError } = require("moleculer").Errors; export default class ImageTransformation extends Service { @@ -23,7 +24,7 @@ export default class ImageTransformation extends Service { mixins: [DbMixin("comics", Comic), BullMqMixin], settings: { bullmq: { - client: process.env.REDIS_URI, + client: pubClient, }, }, hooks: {}, -- 2.49.1