({
+ language: 'en',
+ setLanguage: () => {},
+ translate: async (text) => text
+});
+
+// Typed Higher-Order Component for auto-translating text
+function withAutoTranslation(
+ WrappedComponent: ComponentType
+): ComponentType
{
+ return function TranslatedComponent(props: P) {
+ const { translate } = useContext(TranslationContext);
+
+ // Recursively translate props
+ const translateProps = async (obj: any): Promise => {
+ if (typeof obj === 'string') {
+ return await translate(obj);
+ }
+
+ if (obj && typeof obj === 'object') {
+ const translatedObj: any = {};
+ for (const key of Object.keys(obj)) {
+ translatedObj[key] = await translateProps(obj[key]);
+ }
+ return translatedObj;
+ }
+
+ return obj;
+ };
+
+ // Use state to handle async translations
+ const [translatedProps, setTranslatedProps] = useState(props);
+
+ useEffect(() => {
+ const translateAsyncProps = async () => {
+ const translated = await translateProps(props);
+ setTranslatedProps(translated);
+ };
+
+ translateAsyncProps();
+ }, [props]);
+
+ return ;
+ };
+}
+
+// Translation Provider Component
+const TranslationProvider: React.FC<{children: React.ReactNode}> = ({ children }) => {
+ const [language, setLanguage] = useState('en');
+ const [translations, setTranslations] = useState>({});
+
+ // Function to translate text using Google Translate API
+ const translateText = async (text: string, targetLanguage: string): Promise => {
+ // Check if translation exists in cache first
+ const cacheKey = `${text}_${targetLanguage}`;
+ const cachedTranslation = translations[cacheKey];
+
+ if (cachedTranslation) {
+ return cachedTranslation;
+ }
+
+ try {
+ const response = await fetch('https://translation.googleapis.com/language/translate/v2', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Bearer YOUR_GOOGLE_TRANSLATE_API_KEY'
+ },
+ body: JSON.stringify({
+ q: text,
+ target: targetLanguage
+ })
+ });
+
+ const data = await response.json();
+ const translatedText = data.data.translations[0].translatedText;
+
+ // Cache the translation
+ setTranslations(prev => ({
+ ...prev,
+ [cacheKey]: translatedText
+ }));
+
+ return translatedText;
+ } catch (error) {
+ console.error('Translation error:', error);
+ return text;
+ }
+ };
+
+ // Memoized translation function
+ const translate = React.useCallback(async (text: string): Promise => {
+ if (language === 'en') return text;
+ return await translateText(text, language);
+ }, [language]);
+
+ return (
+
+ {children}
+
+ );
+};
+
+// Example Interface for Component Props
+interface WelcomeProps {
+ title: string;
+ description: string;
+}
+
+// Example Component using Auto-Translation
+const WelcomeComponent = withAutoTranslation(({ title, description }) => {
+ return (
+
+
{title}
+
{description}
+
+ );
+});
+
+// Language Selector Component
+const LanguageSelector: React.FC = () => {
+ const { language, setLanguage } = useContext(TranslationContext);
+
+ return (
+
+
+
+ );
+};
diff --git a/docker-compose.yml b/docker-compose.yml
index 5a7a567..f2a9664 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -66,52 +66,52 @@ services:
# - POSTGRESQL_MASTER_PORT_NUMBER=5432
# - ALLOW_EMPTY_PASSWORD=yes
- timescale-master:
- image: timescale/timescaledb:latest-pg14
- restart: always
- ports:
- - '5434:5432'
- volumes:
- - timescaledb_master_data:/var/lib/postgresql/data
- - ./db.sql:/docker-entrypoint-initdb.d/db.sql
- environment:
- - POSTGRES_USER=postgres
- - POSTGRES_PASSWORD=postgres
- - POSTGRES_DB=development_database
- - TIMESCALEDB_TELEMETRY=off
- - PGDATA=/var/lib/postgresql/data/pgdata
- command: >
- -c wal_level=replica
- -c max_wal_senders=5
- -c max_replication_slots=5
- -c hot_standby=on
+ # timescale-master:
+ # image: timescale/timescaledb:latest-pg14
+ # restart: always
+ # ports:
+ # - '5434:5432'
+ # volumes:
+ # - timescaledb_master_data:/var/lib/postgresql/data
+ # - ./db.sql:/docker-entrypoint-initdb.d/db.sql
+ # environment:
+ # - POSTGRES_USER=postgres
+ # - POSTGRES_PASSWORD=postgres
+ # - POSTGRES_DB=development_database
+ # - TIMESCALEDB_TELEMETRY=off
+ # - PGDATA=/var/lib/postgresql/data/pgdata
+ # command: >
+ # -c wal_level=replica
+ # -c max_wal_senders=5
+ # -c max_replication_slots=5
+ # -c hot_standby=on
- zookeeper:
- image: bitnami/zookeeper:latest
- container_name: zookeeper
- environment:
- - ALLOW_ANONYMOUS_LOGIN=yes
- ports:
- - "2181:2181"
+ # zookeeper:
+ # image: bitnami/zookeeper:latest
+ # container_name: zookeeper
+ # environment:
+ # - ALLOW_ANONYMOUS_LOGIN=yes
+ # ports:
+ # - "2181:2181"
- kafka:
- image: bitnami/kafka:latest
- container_name: kafka
- environment:
- - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
- - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,EXTERNAL1:PLAINTEXT
- - KAFKA_CFG_LISTENERS=INTERNAL://0.0.0.0:9092,EXTERNAL://0.0.0.0:9093,EXTERNAL1://0.0.0.0:9094
- - KAFKA_CFG_ADVERTISED_LISTENERS=INTERNAL://kafka:9092,EXTERNAL://192.168.110.53:9093,EXTERNAL1://192.168.110.53:9094
- - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=INTERNAL
- - ALLOW_PLAINTEXT_LISTENER=yes
- ports:
- - "9092:9092"
- - "9093:9093"
- - "9094:9094"
- depends_on:
- - zookeeper
+ # kafka:
+ # image: bitnami/kafka:latest
+ # container_name: kafka
+ # environment:
+ # - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
+ # - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,EXTERNAL1:PLAINTEXT
+ # - KAFKA_CFG_LISTENERS=INTERNAL://0.0.0.0:9092,EXTERNAL://0.0.0.0:9093,EXTERNAL1://0.0.0.0:9094
+ # - KAFKA_CFG_ADVERTISED_LISTENERS=INTERNAL://kafka:9092,EXTERNAL://192.168.110.53:9093,EXTERNAL1://192.168.110.53:9094
+ # - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=INTERNAL
+ # - ALLOW_PLAINTEXT_LISTENER=yes
+ # ports:
+ # - "9092:9092"
+ # - "9093:9093"
+ # - "9094:9094"
+ # depends_on:
+ # - zookeeper
volumes:
postgresql_master_data:
driver: local
- timescaledb_master_data:
\ No newline at end of file
+ # timescaledb_master_data:
\ No newline at end of file
diff --git a/server2/db/prisma/migrations/20241212073635_/migration.sql b/server2/db/prisma/migrations/20241212073635_/migration.sql
new file mode 100644
index 0000000..69e55f7
--- /dev/null
+++ b/server2/db/prisma/migrations/20241212073635_/migration.sql
@@ -0,0 +1,17 @@
+/*
+ Warnings:
+
+ - Added the required column `apiKey` to the `software_integrations` table without a default value. This is not possible if the table is not empty.
+
+*/
+-- AlterTable
+ALTER TABLE "software_integrations" ADD COLUMN "apiKey" TEXT NOT NULL;
+
+-- CreateTable
+CREATE TABLE "DgmsFiles" (
+ "id" SERIAL NOT NULL,
+ "name" TEXT NOT NULL,
+ "path" TEXT NOT NULL,
+
+ CONSTRAINT "DgmsFiles_pkey" PRIMARY KEY ("id")
+);
diff --git a/server2/db/prisma/schema.prisma b/server2/db/prisma/schema.prisma
index 5ca1411..6902c11 100644
--- a/server2/db/prisma/schema.prisma
+++ b/server2/db/prisma/schema.prisma
@@ -454,3 +454,9 @@ model SoftwareIntegration {
@@map("software_integrations")
}
+
+model DgmsFiles {
+ id Int @id @default(autoincrement())
+ name String
+ path String
+}
\ No newline at end of file
diff --git a/server2/package-lock.json b/server2/package-lock.json
index 05b2321..4e340f2 100644
--- a/server2/package-lock.json
+++ b/server2/package-lock.json
@@ -18,6 +18,7 @@
"express": "^4.21.2",
"jsonwebtoken": "^9.0.2",
"morgan": "^1.10.0",
+ "multer": "^1.4.5-lts.1",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
"zod": "^3.23.8"
@@ -27,6 +28,7 @@
"@types/express": "^5.0.0",
"@types/jsonwebtoken": "^9.0.7",
"@types/morgan": "^1.9.9",
+ "@types/multer": "^1.4.12",
"@types/node": "^22.10.1",
"@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.7",
@@ -709,6 +711,16 @@
"@types/node": "*"
}
},
+ "node_modules/@types/multer": {
+ "version": "1.4.12",
+ "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz",
+ "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*"
+ }
+ },
"node_modules/@types/node": {
"version": "22.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz",
@@ -826,6 +838,12 @@
"node": ">= 8"
}
},
+ "node_modules/append-field": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
+ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
+ "license": "MIT"
+ },
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
@@ -956,6 +974,23 @@
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "license": "MIT"
+ },
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -1030,6 +1065,21 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"license": "MIT"
},
+ "node_modules/concat-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+ "engines": [
+ "node >= 0.8"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ }
+ },
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -1066,6 +1116,12 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"license": "MIT"
},
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+ "license": "MIT"
+ },
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
@@ -1681,6 +1737,12 @@
"node": ">=0.12.0"
}
},
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "license": "MIT"
+ },
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -1874,6 +1936,27 @@
"node": "*"
}
},
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
"node_modules/morgan": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
@@ -1923,6 +2006,24 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
+ "node_modules/multer": {
+ "version": "1.4.5-lts.1",
+ "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
+ "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
+ "license": "MIT",
+ "dependencies": {
+ "append-field": "^1.0.0",
+ "busboy": "^1.0.0",
+ "concat-stream": "^1.5.2",
+ "mkdirp": "^0.5.4",
+ "object-assign": "^4.1.1",
+ "type-is": "^1.6.4",
+ "xtend": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -2083,6 +2184,12 @@
"fsevents": "2.3.3"
}
},
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "license": "MIT"
+ },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -2142,6 +2249,27 @@
"node": ">= 0.8"
}
},
+ "node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "license": "MIT",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/readable-stream/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "license": "MIT"
+ },
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@@ -2328,6 +2456,29 @@
"node": ">= 0.8"
}
},
+ "node_modules/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/string_decoder/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "license": "MIT"
+ },
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -2505,6 +2656,12 @@
"node": ">= 0.6"
}
},
+ "node_modules/typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
+ "license": "MIT"
+ },
"node_modules/typescript": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
@@ -2540,6 +2697,12 @@
"node": ">= 0.8"
}
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -2580,6 +2743,15 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
"node_modules/yaml": {
"version": "2.0.0-1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz",
diff --git a/server2/package.json b/server2/package.json
index 9d8ae5f..d034cf8 100644
--- a/server2/package.json
+++ b/server2/package.json
@@ -16,6 +16,7 @@
"@types/express": "^5.0.0",
"@types/jsonwebtoken": "^9.0.7",
"@types/morgan": "^1.9.9",
+ "@types/multer": "^1.4.12",
"@types/node": "^22.10.1",
"@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.7",
@@ -35,6 +36,7 @@
"express": "^4.21.2",
"jsonwebtoken": "^9.0.2",
"morgan": "^1.10.0",
+ "multer": "^1.4.5-lts.1",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
"zod": "^3.23.8"
diff --git a/server2/src/libs/dgms/dgms.ts b/server2/src/libs/dgms/dgms.ts
new file mode 100644
index 0000000..b826e1c
--- /dev/null
+++ b/server2/src/libs/dgms/dgms.ts
@@ -0,0 +1,115 @@
+import { Request, Response, NextFunction } from 'express'
+import multer from "multer";
+import path from 'path'
+import fs from 'fs/promises'
+import { prisma } from '../../utils/prisma';
+
+// Configure multer for file upload
+const storage = multer.diskStorage({
+ destination: (req, file, cb) => {
+ const uploadPath = path.join(__dirname, '../uploads/dgms')
+ // Ensure upload directory exists
+ fs.mkdir(uploadPath, { recursive: true })
+ .then(() => cb(null, uploadPath))
+ .catch((err) => cb(err, uploadPath))
+ },
+ filename: (req, file, cb) => {
+ const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9)
+ cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname))
+ }
+})
+
+const upload = multer({
+ storage: storage,
+ limits: { fileSize: 50 * 1024 * 1024 }, // 50MB file size limit
+ fileFilter: (req, file, cb) => {
+ const allowedFileTypes = /dgms|txt|pdf|docx?/i
+ const extname = allowedFileTypes.test(path.extname(file.originalname).toLowerCase())
+ const mimetype = allowedFileTypes.test(file.mimetype)
+
+ if (extname && mimetype) {
+ return cb(null, true)
+ } else {
+ cb(new Error('Invalid file type. Only DGMS, TXT, PDF, and DOCX files are allowed.'))
+ }
+ }
+})
+
+export const createDgmsFile = [
+ upload.single('file'),
+ async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ if (!req.file) {
+ return res.status(400).json({ error: 'No file uploaded' });
+ }
+
+ const { originalname, filename, path: filePath } = req.file;
+
+ const dgmsFile = await prisma.dgmsFiles.create({
+ data: {
+ name: originalname,
+ path: filePath,
+ },
+ });
+
+ res.status(201).json({
+ message: 'File uploaded successfully',
+ file: dgmsFile,
+ });
+ } catch (error) {
+ next(error);
+ }
+ },
+ ];
+
+
+// GET: Retrieve all DGMS files
+export const getAllDgmsFiles = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const files = await prisma.dgmsFiles.findMany()
+ res.json(files)
+ } catch (error) {
+ next(error)
+ }
+}
+
+// GET: Retrieve a specific DGMS file by ID
+export const getDgmsFileById = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const { id } = req.params
+ const file = await prisma.dgmsFiles.findUnique({
+ where: { id: parseInt(id) }
+ })
+
+ if (!file) {
+ return res.status(404).json({ error: 'File not found' })
+ }
+
+ res.json(file)
+ } catch (error) {
+ next(error)
+ }
+}
+
+// Serve static files
+export const serveDgmsFile = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const { id } = req.params
+ const file = await prisma.dgmsFiles.findUnique({
+ where: { id: parseInt(id) }
+ })
+
+ if (!file) {
+ return res.status(404).json({ error: 'File not found' })
+ }
+
+ res.download(file.path, file.name, (err) => {
+ if (err) {
+ // Handle download error
+ res.status(500).json({ error: 'Could not download file' })
+ }
+ })
+ } catch (error) {
+ next(error)
+ }
+}
\ No newline at end of file
diff --git a/server2/src/libs/uploads/dgms/file-1733989887141-999734075.pdf b/server2/src/libs/uploads/dgms/file-1733989887141-999734075.pdf
new file mode 100644
index 0000000..0d73a0f
Binary files /dev/null and b/server2/src/libs/uploads/dgms/file-1733989887141-999734075.pdf differ
diff --git a/server2/src/router/dgmsRouter.ts b/server2/src/router/dgmsRouter.ts
new file mode 100644
index 0000000..82f32bc
--- /dev/null
+++ b/server2/src/router/dgmsRouter.ts
@@ -0,0 +1,22 @@
+// @ts-nocheck
+
+import { Router } from "express";
+import {
+ createDgmsFile,
+ getAllDgmsFiles,
+ getDgmsFileById,
+ serveDgmsFile
+} from "../libs/dgms/dgms";
+import { asyncHandler } from "../utils/asyncHandler";
+
+const dgmsRouter = Router();
+
+dgmsRouter.post('/', createDgmsFile);
+
+dgmsRouter.get('/', asyncHandler(getAllDgmsFiles));
+
+dgmsRouter.get('/:id', asyncHandler(getDgmsFileById));
+
+dgmsRouter.get('/:id/download', asyncHandler(serveDgmsFile));
+
+export { dgmsRouter };
\ No newline at end of file
diff --git a/server2/src/router/router.ts b/server2/src/router/router.ts
index b7ae8f8..0cb5c8d 100644
--- a/server2/src/router/router.ts
+++ b/server2/src/router/router.ts
@@ -20,6 +20,7 @@ import { shiftTemplateRouter } from "./shiftTemplateRouter";
import { smpRouter } from "./smpRouter";
import { hazardRouter } from "./hazardRouter";
import { riskAssesmentResponseRouter } from "./riskAssesmentResponseRouter";
+import { dgmsRouter } from "./dgmsRouter";
const router = Router();
@@ -72,4 +73,6 @@ router.use('/riskresponse', riskAssesmentResponseRouter);
router.use('/hazard', hazardRouter);
+router.use('/dgms', dgmsRouter);
+
export { router };