Compare commits

..

49 Commits

Author SHA1 Message Date
010475bca2 added endpoint for group leaving 2024-11-18 13:54:05 +03:00
63aaa30d92 added endpoint for group ownership transfer 2024-11-18 13:34:39 +03:00
947dc720a5 added rename endpoint for groups 2024-11-18 12:32:11 +03:00
7d98ba89c5 fixed wrong action sequence 2024-11-14 03:41:34 +03:00
9988cedbc4 forgot to import 2024-11-13 23:25:58 +03:00
4f5cd78eb0 debug info 2024-11-13 23:09:50 +03:00
0e4cc6507b fixes 2024-11-13 23:08:50 +03:00
1e6975fa13 stupid me 2024-11-13 20:07:59 +03:00
05652f1cfb added persistent volumes for postgresql db 2024-11-13 19:41:30 +03:00
3c7e88d389 deleting old image of an abstract product on its update 2024-11-13 19:38:46 +03:00
eb0c8ac99d deleting abstract product image on its deletion 2024-11-13 19:36:55 +03:00
2be40b6f34 renamed keys in json websocket send on synchronization, upgrading ws to wss 2024-11-13 11:44:37 +03:00
c4f0d65f4f renamed keys in json websocket send on synchronization 2024-11-13 11:38:32 +03:00
66bfba3b8e websockets 2024-11-12 21:22:13 +03:00
0608ccda6d added /status endpoint 2024-11-12 12:31:56 +03:00
c39e8c9790 added endpoints for object deletion 2024-11-11 01:11:24 +03:00
77d95824f3 fixed wrong date inserting 2024-11-09 03:55:41 +03:00
757f5c13a5 fixes 2024-11-07 23:54:51 +03:00
7a89ce2840 added endpoints for retreiving info about items 2024-11-07 21:44:10 +03:00
5853023069 now /api/user/synchronize calculates hashes for items 2024-11-07 03:30:27 +03:00
1a557fd530 fixed /api/user/synchronize 2024-11-07 03:11:07 +03:00
646bc77aa0 shitty naming 2 2024-11-06 16:22:37 +03:00
1099738ac3 shitty naming 2024-11-06 16:19:20 +03:00
66fff16d30 Typescript's better. 2024-11-06 03:48:45 +03:00
96133b7733 added test if there's no group admin 2024-11-04 04:42:04 +03:00
693a0980d9 added tesf for NaN in existance.groupExists 2024-11-04 04:30:29 +03:00
69c2255614 now sending userid on login and register 2024-11-03 02:41:13 +03:00
0b32306f84 added new endpoint 2024-11-03 00:01:54 +03:00
3858a3c06e added new endpoint 2024-11-02 22:32:35 +03:00
c34cd7f809 added new endpoint 2024-11-02 22:15:24 +03:00
317fba81aa added 2 new endpoints 2024-11-02 21:51:29 +03:00
07ffeb8de4 fixed wrong variable name 2024-11-02 12:49:52 +03:00
c869f7c6ac sending new group id in response to group creation 2024-11-02 12:45:36 +03:00
02b1a572d1 added endpoints for changing password and username 2024-11-02 01:36:25 +03:00
afa1edeb8d fixed sending int 2024-10-31 12:37:17 +03:00
f6e25d606a fix property access 2024-10-31 10:10:27 +03:00
6adb48aeab check for group name 2024-10-31 10:06:49 +03:00
d809abd17b mistake 2024-10-31 09:50:17 +03:00
9cd1debd4a added new endpoint 2024-10-31 09:00:44 +03:00
d4233c72f5 translated error 2024-10-31 08:29:56 +03:00
c950882e0e right container start sequence 2024-10-31 07:52:37 +03:00
ad5d7c4547 added config.json to gitignore 2024-10-31 07:48:17 +03:00
c9e679ec4c tested and fixed 2024-10-31 07:47:27 +03:00
7a2ab7dd5b Centralized errors and added their translations. Didn't test 2024-10-31 06:33:57 +03:00
47e90764e7 fixed fs.renameSync & docker issue 2024-10-28 22:04:50 +03:00
155faa6731 move config to sample 2024-10-28 20:04:00 +03:00
f2945fc92d docker deployment 2024-10-28 19:35:35 +03:00
0e9a7f0856 ability to upload images 2024-10-28 17:19:02 +03:00
f720f20db0 ability to upload images 2024-10-28 17:17:47 +03:00
37 changed files with 1478 additions and 356 deletions

5
.gitignore vendored
View File

@@ -135,3 +135,8 @@ dist
.dtmp
.bkp
#Local
uploads
temp
config.json

7
Dockerfile Normal file
View File

@@ -0,0 +1,7 @@
FROM node:22-bullseye
COPY . .
RUN npm i
EXPOSE 3000
ENTRYPOINT ["node", "./src/index.js"]

View File

@@ -1,2 +1,9 @@
# bsfe_server
## Deployment
1.Copy ``sample.config.json`` to ``config.json``
2. Edit ``config.json`` to your needs (port, database credentials)
3. Edit ``docker-compose.yml`` (insert changed port and database credentials from ``config.json``)
4. First-time: ``docker-compose up --build``
5. Then just: ``docker-compose up -d``

23
docker-compose.yml Normal file
View File

@@ -0,0 +1,23 @@
services:
bsfe:
build: .
ports:
- 8012:8080
- 8282:8282
depends_on:
- database
restart: on-failure
volumes:
- ./data/temp:/temp
- ./data/uploads:/uploads
database:
image: 'postgres:15'
volumes:
- ./data/db:/var/lib/postgresql/data
environment:
POSTGRES_USER: bsfe
POSTGRES_PASSWORD: Ch@NgEME!
POSTGRES_DB: bsfe
healthcheck:
test: ["CMD", "pg_isready", "-U", "bsfe"]

54
messages/en-US/msgs.json Normal file
View File

@@ -0,0 +1,54 @@
{
"authentication not found": "Authentication token not found!",
"authentication invalid": "Authentication token is invalid",
"user duplicate": "Such user already exists!",
"user invalid syntax": "Invalid syntax in one of user's parameters!",
"user already in group": "User is already in group!",
"user not found": "User does not exists!",
"admin leave": "You are an admin of that group. You must transfer ownership of that group first!",
"username taken": "Username is taken!",
"username not found": "Such username not found!",
"username required": "Username is required!",
"password required": "Password is required!",
"password invalid": "Wrong password!",
"group name taken": "Group name is taken!",
"group name not found": "Group with such name not found!",
"group id not found": "Group with such ID not found!",
"group not an owner": "You are not an owner of this group!",
"group not a member": "You are not a member of this group!",
"name not specified": "New name of a group is not specified!",
"new owner not specified": "ID of a new group owner is not specified!",
"abstract product not found": "Such abstract product is not found!",
"abstract product invalid syntax": "Invalid syntax in one of user's parameters!",
"barcode not found": "Such barcode not found!",
"barcode duplicate": "Such barcode already exists!",
"barcode too long": "This barcode is too long!",
"barcode invalid syntax": "Invalid syntax in barcode!",
"abstract product name not found": "Abstract product with such name not found!",
"abstract product name duplicate": "Abstract product with such name already exists!",
"abstract product name too long": "Abstract product name is too long!",
"abstract product name invalid syntax": "Invalid syntax in abstract product name!",
"net weight too long": "Specified net weight is too long!",
"net weight invalid syntax": "Invalid syntax in specified net weight!",
"image hash too long": "Specified image hash is too long!",
"image hash invalid syntax": "Invalid syntax in specified image hash!",
"category too long": "Specified category is too long!",
"category invalid syntax": "Invalid syntax in specified category!",
"category not found": "Such category not found!",
"unit too long": "Specified unit is too long!",
"unit invalid syntax": "Invalid syntax in specified unit!",
"product not found": "Such product not found!",
"product invalid syntax": "Invalid syntax in one of product's parameters!",
"abstract product id not found": "Specified abstract product id not found!",
"abstract product id invalid syntax": "Invalid syntax in abstract product id!",
"expiry date too long": "Specified expiry date is too long!",
"expiry date invalid syntax": "Invalid syntax in specified expiry date!",
"categories not found": "Such category not found!",
"categories duplicate": "Such category already exists!",
"categories invalid syntax": "Invalid syntax in one of category parameters!",
"ok": "Successfull.",
"invalid syntax": "Invalid syntax!",
"png only": "Only .png files are allowed!",
"too long": "Specified value is too long!",
"unknown": "Unknown server error. Please, contact the develoeper!"
}

54
messages/ru-RU/msgs.json Normal file
View File

@@ -0,0 +1,54 @@
{
"authentication not found": "Токен аутентификации не найден!",
"authentication invalid": "Токен аутентификации неверен!",
"user duplicate": "Такой пользователь уже существует!",
"user invalid syntax": "Неправильный синткасис в одном из параметров пользователя!",
"user already in group": "Пользователь уже в группе!",
"user not found": "Пользователь не существует!",
"admin leave": "Вы являетесь администратором группы. Перед выходом передайте владение группой!",
"username taken": "Имя пользователя занято!",
"username not found": "Такое имя пользователя не найдено!",
"username required": "Требуется имя пользователя!",
"password required": "Требуется пароль!",
"password invalid": "Неверный пароль!",
"group name taken": "Такое имя группы уже существует!",
"group name not found": "Группы с таким именем не существует!",
"group id not found": "Группы с таким ID не существует!",
"group not an owner": "Вы не владелец этой группы!",
"group not a member": "Вы не участник этой группы!",
"name not specified": "Не указано новое имя группы!",
"new owner not specified": "Не указан ID нового владельца группы!",
"abstract product not found": "Такой абстрактный продукт не найден!",
"abstract product invalid syntax": "Неправильный синткасис в одном из параметров абстрактного продукта!",
"barcode not found": "Такой штрихкод не найден!",
"barcode duplicate": "Такой штрихкод уже существует!",
"barcode too long": "Этот штрихкод слишком большой!",
"barcode invalid syntax": "Этот штрихкод неверен!",
"abstract product name not found": "абстрактного продукта с таким именем не найдено!",
"abstract product name duplicate": "абстрактный продукт с таким именем уже существует!",
"abstract product name too long": "Имя абстрактного продукта слишком длинное!",
"abstract product name invalid syntax": "Неверный синткасис в имени абстрактного продукта!",
"net weight too long": "Указанный вес нетто слишком длинный!",
"net weight invalid syntax": "Неверный указанный вес нетто!",
"image hash too long": "Указанный хеш изображения слишком длинный!",
"image hash invalid syntax": "Неверный указанный хеш изображения!",
"category too long": "Указанная категория слишком длинная!",
"category invalid syntax": "Неверный синткасис в категории!",
"category not found": "Такая категория не найдена!!",
"unit too long": "Указанная единица измерения слишком длинная!",
"unit invalid syntax": "Неправильный синткасис в указанной единице измерения!",
"product not found": "Такой продукт не найден!",
"product invalid syntax": "Неверный синткасис в одном из параметров продукта!",
"abstract product id not found": "Абстрактный продукт с таким ID не найден!",
"abstract product id invalid syntax": "Неверный синткасис ID абстрактного продукта!",
"expiry date too long": "Указанная дата годен до слишком длинная!",
"expiry date invalid syntax": "Неверный синткасис в указанной дате годен до!",
"categories not found": "Накая категория не найдена!",
"categories duplicate": "Такая категория уже существует!",
"categories invalid syntax": "Неверный синткасис одного из параметров категории!",
"ok": "Успешно.",
"invalid syntax": "Неправильный синткасис!",
"png only": "Разрешены только файлы формата .png!",
"too long": "Указанное значение слишком длинное!",
"unknown": "Неизвестная ошибка сервера! Пожалуйста, сообщите разработчику!"
}

392
package-lock.json generated
View File

@@ -12,8 +12,12 @@
"bcrypt": "^5.1.1",
"express": "^4.21.1",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"nodemon": "^3.1.7",
"pg": "^8.13.1"
"path": "^0.12.7",
"pg": "^8.13.1",
"websocket": "^1.0.35",
"ws": "^8.18.0"
}
},
"node_modules/@mapbox/node-pre-gyp": {
@@ -89,6 +93,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/aproba": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
@@ -214,6 +224,36 @@
"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/bufferutil": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz",
"integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"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",
@@ -290,6 +330,51 @@
"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/concat-stream/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/concat-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/concat-stream/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/console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
@@ -332,6 +417,25 @@
"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/d": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
"integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==",
"license": "ISC",
"dependencies": {
"es5-ext": "^0.10.64",
"type": "^2.7.2"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
@@ -451,12 +555,67 @@
"node": ">= 0.4"
}
},
"node_modules/es5-ext": {
"version": "0.10.64",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
"integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
"hasInstallScript": true,
"license": "ISC",
"dependencies": {
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
"esniff": "^2.0.1",
"next-tick": "^1.1.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
"license": "MIT",
"dependencies": {
"d": "1",
"es5-ext": "^0.10.35",
"es6-symbol": "^3.1.1"
}
},
"node_modules/es6-symbol": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz",
"integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==",
"license": "ISC",
"dependencies": {
"d": "^1.0.2",
"ext": "^1.7.0"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/esniff": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz",
"integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
"license": "ISC",
"dependencies": {
"d": "^1.0.1",
"es5-ext": "^0.10.62",
"event-emitter": "^0.3.5",
"type": "^2.7.2"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -466,6 +625,16 @@
"node": ">= 0.6"
}
},
"node_modules/event-emitter": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
"integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
"license": "MIT",
"dependencies": {
"d": "1",
"es5-ext": "~0.10.14"
}
},
"node_modules/express": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
@@ -523,6 +692,15 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/ext": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
"license": "ISC",
"dependencies": {
"type": "^2.7.2"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -911,6 +1089,18 @@
"node": ">=0.12.0"
}
},
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
"license": "MIT"
},
"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/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
@@ -1092,6 +1282,15 @@
"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/minipass": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
@@ -1144,6 +1343,36 @@
"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/multer/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/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -1153,6 +1382,12 @@
"node": ">= 0.6"
}
},
"node_modules/next-tick": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==",
"license": "ISC"
},
"node_modules/node-addon-api": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
@@ -1179,6 +1414,17 @@
}
}
},
"node_modules/node-gyp-build": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.3.tgz",
"integrity": "sha512-EMS95CMJzdoSKoIiXo8pxKoL8DYxwIZXYlLmgPb8KUv794abpnLK6ynsCAWNliOjREKruYKdzbh76HHYUHX7nw==",
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/nodemon": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz",
@@ -1295,6 +1541,16 @@
"node": ">= 0.8"
}
},
"node_modules/path": {
"version": "0.12.7",
"resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
"integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==",
"license": "MIT",
"dependencies": {
"process": "^0.11.1",
"util": "^0.10.3"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -1450,6 +1706,21 @@
"node": ">=0.10.0"
}
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
}
},
"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",
@@ -1734,6 +2005,14 @@
"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.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -1834,6 +2113,12 @@
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/type": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz",
"integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==",
"license": "ISC"
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@ -1847,6 +2132,21 @@
"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/typedarray-to-buffer": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
"license": "MIT",
"dependencies": {
"is-typedarray": "^1.0.0"
}
},
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
@@ -1862,12 +2162,40 @@
"node": ">= 0.8"
}
},
"node_modules/utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/util": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
"integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
"license": "MIT",
"dependencies": {
"inherits": "2.0.3"
}
},
"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/util/node_modules/inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
"license": "ISC"
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -1892,6 +2220,38 @@
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/websocket": {
"version": "1.0.35",
"resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz",
"integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==",
"license": "Apache-2.0",
"dependencies": {
"bufferutil": "^4.0.1",
"debug": "^2.2.0",
"es5-ext": "^0.10.63",
"typedarray-to-buffer": "^3.1.5",
"utf-8-validate": "^5.0.2",
"yaeti": "^0.0.6"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/websocket/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/websocket/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@@ -1917,6 +2277,27 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -1926,6 +2307,15 @@
"node": ">=0.4"
}
},
"node_modules/yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
"integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==",
"license": "MIT",
"engines": {
"node": ">=0.10.32"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",

View File

@@ -16,8 +16,12 @@
"bcrypt": "^5.1.1",
"express": "^4.21.1",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"nodemon": "^3.1.7",
"pg": "^8.13.1"
"path": "^0.12.7",
"pg": "^8.13.1",
"websocket": "^1.0.35",
"ws": "^8.18.0"
},
"type": "module"
}

View File

@@ -1,6 +1,7 @@
{
"debug": true,
"port": "8081",
"wsport": "8282",
"dbuser": "bsfe",
"dbhost": "localhost",
"dbport": "5432",

View File

@@ -1,29 +1,63 @@
import AbstractProductService from '../services/abstractproduct.js';
import statuses from '../utils/status.js';
import log from '../utils/log.js';
import ProductService from '../services/product.js';
import fs from 'fs';
import path from 'path';
import customError from '../response/customError.js';
import responseCodes from '../response/responseCodes.js';
import translate from '../utils/translate.js';
import { createHash } from 'crypto';
import notify from '../utils/notify.js';
const TAG = "/controllers/abstractproduct.js";
class AbstractProductController {
async create(req, res) {
try {
const { groupId, localId, barcode, name, net_weight, image_filename, category, unit } = req.body;
const { groupId, localId, barcode, name, net_weight, category, unit } = req.body;
if (!req.file) throw new customError(`user hasn't supplied a file for abstract product creation`, responseCodes.responses.image_filename)
const tempPath = req.file.path;
const image_buffer = fs.readFileSync(tempPath);
const image_filename = createHash('md5').update(image_buffer).digest('hex');
const targetPath = path.join(path.resolve(path.dirname('')), `/uploads/${image_filename}.png`);
if (path.extname(req.file.originalname).toLowerCase() !== ".png") {
fs.rmSync(tempPath);
throw new customError(`create abstract product only png allowed`, responseCodes.responses.general.png_only);
}
fs.copyFileSync(tempPath, targetPath);
fs.rmSync(tempPath);
await AbstractProductService.create(groupId, localId, barcode, name, net_weight, image_filename, category, unit);
return res.status(200).send("Successfull");
} catch (e) {
switch (e.status) {
case statuses.duplicate:
return res.status(400).send(e.message);
default:
log.error(e.original);
return res.status(500).send(e.message);
}
}
notify(req.headers.authorization.split(' ')[1], groupId, 'create', 'abstractproduct', {
local_id: localId, barcode, name, net_weight, image_filename, category, unit
});
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok));
}
async update(req, res) {
try {
let { groupId, localId, barcode, name, net_weight, image_filename, category, unit } = req.body;
let { groupId, localId, barcode, name, net_weight, category, unit } = req.body;
var tempPath, image_buffer, image_filename, targetPath;
if (req.file) {
tempPath = req.file.path;
let previousImageHash = (await AbstractProductService.getByLocalId(groupId, localId)).image_filename
let previousImagePath = path.join(path.resolve(path.dirname('')), `/uploads/${previousImageHash}.png`);
fs.unlinkSync(previousImagePath)
image_buffer = fs.readFileSync(tempPath);
image_filename = createHash('md5').update(image_buffer).digest('hex');
targetPath = path.join(path.resolve(path.dirname('')) + `/uploads/${image_filename}.png`);
fs.copyFileSync(tempPath, targetPath);
fs.rmSync(tempPath);
}
if (barcode) await AbstractProductService.updateBarcode(groupId, localId, barcode);
@@ -31,23 +65,50 @@ class AbstractProductController {
if (net_weight) await AbstractProductService.updateNetWeight(groupId, localId, net_weight);
if (image_filename) await AbstractProductService.updateImageFilename(groupId, localId, image_filename);
if (tempPath) await AbstractProductService.updateImageFilename(groupId, localId, image_filename);
if (category) await AbstractProductService.updateCategory(groupId, localId, category);
if (unit) await AbstractProductService.updateUnit(groupId, localId, unit);
return res.status(200).send("Successfull");
} catch (e) {
switch (e.status) {
case statuses.invalid_syntax:
log.error(e.original);
return res.status(400).send(e.message);
default:
log.error(e.original);
return res.status(500).send(e.message);
let data = await AbstractProductService.getByLocalId(groupId, localId)
notify(req.headers.authorization.split(' ')[1], groupId, 'update', 'abstractproduct', data);
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok));
}
async getById(req, res) {
let { localId, groupId } = req.params
let result = await AbstractProductService.getByLocalId(groupId, localId)
return res.status(200).send(result)
}
async getImage(req, res) {
let { localId, groupId } = req.params;
let imageFilename = (await AbstractProductService.getByLocalId(groupId, localId)).image_filename
let imagePath = path.join(path.resolve(path.dirname('')), `/uploads/${imageFilename}.png`);
let image = fs.readFileSync(imagePath)
return res.status(200).send(image)
}
async delete(req, res) {
let { localId, groupId } = req.params;
let imageFilename = (await AbstractProductService.getByLocalId(groupId, localId)).image_filename
let imagePath = path.join(path.resolve(path.dirname('')), `/uploads/${imageFilename}.png`);
fs.unlinkSync(imagePath)
await AbstractProductService.delete(groupId, localId)
notify(req.headers.authorization.split(' ')[1], groupId, 'delete', 'abstractproduct', { local_id: localId });
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok))
}
};

View File

@@ -1,25 +1,51 @@
import CategoryService from "../services/category.js";
import log from "../utils/log.js";
import translate from "../utils/translate.js";
import responseCodes from "../response/responseCodes.js";
import notify from "../utils/notify.js";
const TAG = "controllers/category.js";
class CategoryController {
async create(req, res) {
try {
const { localId, categoryName, groupId } = req.body;
await CategoryService.create(groupId, localId, categoryName);
return res.status(200).send("Success");
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/create: ${e}`)); }
notify(req.headers.authorization.split(' ')[1], groupId, 'create', 'category', {
local_id: localId, name: categoryName
});
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok));
}
async update(req, res) {
try {
const { localId, groupId, name } = req.body;
const { localId, groupId, categoryName } = req.body;
await CategoryService.update(groupId, localId, name);
return res.status(200).send("Success");
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/update: ${e}`)); }
await CategoryService.update(groupId, localId, categoryName);
let data = await CategoryService.getById(groupId, localId)
notify(req.headers.authorization.split(' ')[1], groupId, 'update', 'category', data);
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok));
}
async getByLocalId(req, res) {
const { groupId, localId } = req.params;
let result = await CategoryService.getById(groupId, localId)
return res.status(200).send(result)
}
async delete(req, res) {
const { groupId, localId } = req.params;
await CategoryService.delete(groupId, localId);
notify(req.headers.authorization.split(' ')[1], groupId, 'delete', 'category', { local_id: localId });
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok))
}
};

View File

@@ -2,49 +2,112 @@ import GroupService from '../services/group.js';
import UserService from '../services/user.js';
import jwt from 'jsonwebtoken';
import config from '../../config.json' with {type: "json"};
import statuses from '../utils/status.js';
import log from '../utils/log.js';
import translate from '../utils/translate.js';
import responseCodes from '../response/responseCodes.js';
import customError from '../response/customError.js';
const TAG = "/controllers/group.js";
class GroupController {
async create(req, res) {
try {
let { groupName } = req.params;
let user = jwt.decode(req.headers.authorization.split(' ')[1], config.secret);
let status = await GroupService.create(groupName, user.login.id);
let group = await GroupService.create(groupName, user.login.id);
log.info(`New group with name ${groupName} was just created by user ${user.login.username}`);
UserService.joinGroup(user.login.id, status.id);
return res.status(200).send("Successfull");
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/create: ${e}`)); }
await UserService.joinGroup(user.login.id, group.id);
return res.status(200).send(group.id.toString())
}
async join(req, res) {
try {
let { groupId } = req.params;
let user = jwt.decode(req.headers.authorization.split(' ')[1], config.secret);
let status = await UserService.joinGroup(user.login.id, groupId);
await UserService.joinGroup(user.login.id, groupId);
if (status == statuses.duplicate) return res.status(400).send("Already in group");
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok));
}
log.info(`User ${user.login.username} has just joined group with ID ${groupId}`);
return res.status(200).send("Successfull");
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/join: ${e}`)); }
async leave(req, res) {
let groupId = req.params.groupId;
let user = jwt.decode(req.headers.authorization.split(' ')[1], config.secret);
if (await GroupService.getAdminId(groupId) == user.login.id) {
let code = responseCodes.responses.user.admin_leave
return res.status(responseCodes.getHTTPCode(code)).send(translate(req.headers["accept-language"], code))
}
await UserService.leaveGroup(user.login.id, groupId);
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok));
}
async updatePassword(req, res) {
try {
let { groupId } = req.params;
let { password } = req.body;
await GroupService.updatePassword(groupId, password);
log.info(`Password for group with ID ${groupId} was updated`);
return res.status(200).send("Successfull");
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/updatePassword ${e}`)); }
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok));
}
async getByName(req, res) {
let groupName = req.params.groupName || req.body.groupName;
let group = await GroupService.getByName(groupName);
return res.status(200).send(group["id"].toString());
}
async getById(req, res) {
let groupId = req.params.groupId;
let groupName = (await GroupService.getById(groupId)).name;
return res.status(200).send(groupName);
}
async getUsersInGroup(req, res) {
const groupId = req.params.groupId;
let result = await GroupService.getUsersInGroup(groupId);
return res.status(200).send(result);
}
async getAdminId(req, res) {
const groupId = req.params.groupId;
let result = await GroupService.getAdminId(groupId);
if (!result) result = -1
return res.status(200).send(result.toString())
}
async rename(req, res) {
const groupId = req.params.groupId;
const name = req.body.name;
if (!name) throw new customError(`New group name is not specified`, responseCodes.responses.groups.name_not_specified);
await GroupService.rename(groupId, name);
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok));
}
async transferOwnership(req, res) {
const groupId = req.params.groupId;
const userId = req.body.userId;
if (!userId) throw new customError(`New owner id is not specified`, responseCodes.responses.groups.new_owner_not_specified);
await GroupService.transferOwnership(groupId, userId);
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok));
}
}

View File

@@ -1,29 +1,23 @@
import ProductService from '../services/product.js';
import statuses from '../utils/status.js';
import log from '../utils/log.js';
import translate from '../utils/translate.js';
import responseCodes from '../response/responseCodes.js';
import notify from '../utils/notify.js';
const TAG = "/controllers/product.js"
class AbstractProductController {
async create(req, res) {
try {
const { groupId, localId, abstract_product_id, amount, date_of_production, expiry_date } = req.body;
await ProductService.create(groupId, localId, abstract_product_id, amount, date_of_production, expiry_date);
return res.status(200).send("Successfull");
} catch (e) {
switch (e.status) {
case statuses.duplicate:
return res.status(400).send(e.message);
default:
log.error(e.original)
return res.status(500).send(e.message)
}
}
}
await ProductService.create(groupId, localId, abstract_product_id, amount, date_of_production, expiry_date);
let data = await ProductService.getByLocalId(groupId, localId)
notify(req.headers.authorization.split(' ')[1], groupId, 'create', 'product', data);
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok));
}
async update(req, res) {
try {
let { groupId, localId, abstract_product_id, amount, date_of_production, expiry_date } = req.body;
if (abstract_product_id) await ProductService.updateAbstractProductId(groupId, localId, abstract_product_id);
@@ -33,15 +27,32 @@ class AbstractProductController {
if (date_of_production) await ProductService.updateDateOfProduction(groupId, localId, date_of_production);
if (expiry_date) await ProductService.updateExpiryDate(groupId, localId, expiry_date);
} catch (e) {
switch (e.status) {
case statuses.invalid_syntax:
log.error(e.original);
return res.status(400).send(e.message);
}
let data = await ProductService.getByLocalId(groupId, localId)
notify(req.headers.authorization.split(' ')[1], groupId, 'update', 'product', data);
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok));
}
return res.status(200).send("Successfull");
async getByLocalId(req, res) {
let { groupId, localId } = req.params;
let result = await ProductService.getByLocalId(groupId, localId)
return res.status(200).send(result)
}
async delete(req, res) {
let { groupId, localId } = req.params;
let id = (await ProductService.getByLocalId(groupId, localId)).id
await ProductService.delete(id)
notify(req.headers.authorization.split(' ')[1], groupId, 'delete', 'product', { local_id: localId });
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok));
}
};

View File

@@ -1,47 +1,93 @@
import UserService from '../services/user.js';
import log from '../utils/log.js';
import bcrypt from 'bcrypt';
import genToken from '../utils/jwt.js';
import jwtutils from '../utils/jwt.js';
import AbstractProductService from '../services/abstractproduct.js';
import ProductService from '../services/product.js';
import CategoryService from '../services/category.js';
import translate from '../utils/translate.js';
import responseCodes from '../response/responseCodes.js';
import customError from '../response/customError.js';
import { createHash } from 'crypto';
const TAG = "/controllers/userjs"
class UserController {
async register(req, res) {
try {
const { username, password } = req.body;
await UserService.create(username, password);
let userId = await UserService.create(username, password);
log.info(`New user with name ${username} has just registered`);
return res.status(200).send("Successfull register");
} catch (e) { res.status(500).send(log.unknownError(`${TAG}/register: ${e}`)); }
return res.status(200).send(userId.toString());
}
async login(req, res) {
try {
const { username, password } = req.body;
const user = await UserService.getByUsername(username);
if (!bcrypt.compareSync(password, user.password)) return res.status(401).send("Wrong password");
if (!bcrypt.compareSync(password, user.password)) throw new customError(`Wrong user password`, responseCodes.responses.passwords.invalid);
const token = genToken(user);
return res.status(200).send(token);
} catch (e) { res.status(500).send(log.unknownError(`${TAG}/login: ${e}`)); }
const token = jwtutils.genToken(user);
return res.status(200).send({ id: user.id, token: token });
}
async synchronize(req, res) {
try {
const { groupId } = req.params;
let result = {};
result.abstract_products = await AbstractProductService.getAll(groupId);
result.products = await ProductService.getAll(groupId);
result.categories = await CategoryService.getAll(groupId);
result.abstract_products.forEach((abstractproduct) => {
let data = `${abstractproduct.local_id}:${abstractproduct.barcode}:${abstractproduct.name}:${abstractproduct.net_weight}:${abstractproduct.image_filename}:${abstractproduct.category}:${abstractproduct.unit}`
abstractproduct.hash = createHash('md5').update(data).digest('hex');
});
return res.status(200).json(result);
} catch (e) { res.status(500).send(log.unknownError(`${TAG}/synchronize: ${e}`)); }
result.products = await ProductService.getAll(groupId);
result.products.forEach((product) => {
let data = `${product.local_id}:${product.abstract_product_id}:${product.amount}:${new Date(product.date_of_production).valueOf() / 1000}:${new Date(product.expiry_date).valueOf() / 1000}`
product.hash = createHash('md5').update(data).digest('hex');
});
result.categories = await CategoryService.getAll(groupId);
result.categories.forEach((category) => {
let data = `${category.local_id}:${category.name}`
category.hash = createHash('md5').update(data).digest('hex');
});
return res.status(200).send(result);
}
async changeUsername(req, res) {
const userId = jwtutils.getUserIdFromToken(req.headers.authorization.split(' ')[1]);
const { username } = req.body;
await UserService.changeUsername(userId, username);
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok))
}
async changePassword(req, res) {
const userId = jwtutils.getUserIdFromToken(req.headers.authorization.split(' ')[1]);
const { password } = req.body;
await UserService.changePassword(userId, password);
return res.status(200).send(translate(req.headers["accept-language"], responseCodes.responses.general.ok))
}
async getAllGroupsForUser(req, res) {
const userId = jwtutils.getUserIdFromToken(req.headers.authorization.split(' ')[1]);
let result = await UserService.getAllGroupsForUser(userId);
return res.status(200).send(result);
}
async getById(req, res) {
const userId = req.params.userId
let result = (await UserService.getById(userId)).username;
return res.status(200).send(result);
}
}

View File

@@ -1,6 +1,6 @@
import pg from 'pg';
import log from './utils/log.js'
import fs from 'fs'
import fs from 'fs';
import config from '../config.json' with {type: "json"};
const { Pool } = pg;

View File

@@ -1,5 +1,6 @@
import express from 'express';
import UserRouter from './routers/user.js';
import UserService from './services/user.js';
import GroupRouter from './routers/group.js';
import AbstractProductRouter from './routers/abstractproduct.js';
import log from './utils/log.js';
@@ -8,10 +9,16 @@ import config from '../config.json' with {type: "json"};
import ProductRouter from './routers/product.js';
import CategoryRouter from './routers/category.js';
const app = express();
import { WebSocketServer } from 'ws';
import jwt from 'jsonwebtoken';
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
const app = express();
const wss = new WebSocketServer({ port: config.wsport });
const clients = [];
app.use(express.urlencoded({ extended: false, limit: "200mb", parameterLimit: 100000 }));
app.use(express.json({ limit: "200mb", parameterLimit: 1000000 }));
app.use('/api/user/', UserRouter);
app.use('/api/group/', GroupRouter);
@@ -19,6 +26,59 @@ app.use('/api/abstractproduct', AbstractProductRouter);
app.use('/api/product', ProductRouter);
app.use('/api/category', CategoryRouter);
app.listen(config.port, () => {
log.info(`Application has started on port ${config.port}`)
app.get('/status', (req, res) => {
return res.status(200).send("All OK");
});
wss.on('connection', (client) => {
client.on('message', async (message) => {
if (message == "keepalive") return;
let parsed = JSON.parse(message);
let token = parsed.token;
let currentGroup = parsed.currentGroup;
try {
if (!jwt.verify(token, config.secret)) {
client.send("Invalid token");
return;
}
if (!await UserService.isInGroup(jwt.decode(token, config.secret).login.id, currentGroup)) {
client.send("Not a member of specified group");
return;
}
} catch (e) {
log.error("Error during connection through websocket.")
client.send("Error")
return;
}
clients.push({
socket: client,
token,
currentGroup
});
});
client.on('close', () => {
for (let i = 0; i < clients.length; i++) {
if (clients[i].socket == client) {
log.debug(`Client with token ${clients[i].token} has disconnected`)
clients.splice(i, 1);
break;
}
}
})
});
const server = app.listen(config.port, () => {
log.info(`Application has started on port ${config.port}`);
});
server.on('upgrade', (req, res, head) => {
wss.handleUpgrade(req, res, head, socket => {
wss.emit('connection', socket, req);
});
});
export default clients;

View File

@@ -1,48 +1,47 @@
import log from '../utils/log.js'
import jwt from 'jsonwebtoken';
import config from '../../config.json' with {type: "json"};
import GroupService from '../services/group.js';
import UserService from '../services/user.js';
import customError from '../response/customError.js';
import responseCodes from '../response/responseCodes.js';
const TAG = "/middlewares/auth.js";
const requireUsername = async (req, res, next) => {
if (req.method == "OPTIONS") next();
try {
const { username } = req.body;
if (!username) return res.status(400).send("Username is required");
if (!username) throw new customError(`requireUsername username is required`, responseCodes.responses.usernames.required)
next();
} catch (e) { return res.status(500).send(unknownError(`${TAG}/requireUsername: ${e}`)); }
};
const requirePassword = async (req, res, next) => {
if (req.method == "OPTIONS") next();
try {
const { password } = req.body;
if (!password) return res.status(400).send("Password is required");
if (!password) throw new customError(`requirePassword password is required`, responseCodes.responses.passwords.required);
next();
} catch (e) { return res.status(500).send(unknownError(`${TAG}/requirePassword: ${e}`)); }
};
const authenticate = async (req, res, next) => {
if (req.method == "OPTIONS") next();
if (!req.headers.authorization) throw new customError(`authenticate no authorization header`, responseCodes.responses.authentication.not_found);
try {
if (!req.headers.authorization) return res.status(403).send("No authorization header supplied");
const token = req.headers.authorization.split(' ')[1];
if (!token) return res.status(403).send("No authorization token supplied");
if (!jwt.verify(token, config.secret)) return res.status(403).send("Authorization token is incorrect");
if (!token) throw new customError(`authenticate no authorization token in header`, responseCodes.responses.authentication.not_found);
let user = jwt.decode(token, config.secret);
await UserService.getByUsername(user.login.username)
if (!jwt.verify(token, config.secret)) throw new customError(`authenticate token is invalid`, responseCodes.responses.authentication.invalid);
next();
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/authenticate: ${e}`)); }
};
const authorizeGroupOwner = async (req, res, next) => {
if (req.method == "OPTIONS") next();
try {
const token = req.headers.authorization.split(' ')[1];
const { groupId } = req.params;
@@ -50,24 +49,21 @@ const authorizeGroupOwner = async (req, res, next) => {
let user = jwt.decode(token, config.secret);
let adminId = await GroupService.getAdminId(groupId);
if (user.login.id != adminId) return res.status(403).send("Not your group");
if (user.login.id != adminId) throw new customError(`authorizeGroupOwner not an owner`, responseCodes.responses.groups.not_an_owner)
next();
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/authorizeGroupOwner: ${e}`)); }
};
const checkGroupPassword = async (req, res, next) => {
if (req.method == "OPTIONS") next();
try {
const { groupId } = req.params;
const { password } = req.body;
const groupPassword = await GroupService.getPassword(groupId);
if (groupPassword != password) return res.status(403).send("Wrong password");
if (groupPassword != password) throw new customError(`checkGroupPassword password is invalid`, responseCodes.responses.passwords.invalid);
next();
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/checkGroupPassword: ${e}`)); }
};
const userIsInGroup = async (req, res, next) => {
@@ -77,7 +73,7 @@ const userIsInGroup = async (req, res, next) => {
const token = req.headers.authorization.split(' ')[1];
let user = jwt.decode(token, config.secret);
if (!await UserService.isInGroup(user.login.id, groupId)) return res.status(403).send("You are not a member of this group");
if (!await UserService.isInGroup(user.login.id, groupId)) throw new customError(`userIsInGroup not a member`, responseCodes.responses.groups.not_a_member)
next();
};

View File

@@ -1,116 +1,33 @@
import UserService from '../services/user.js';
import customError from '../response/customError.js';
import responseCodes from '../response/responseCodes.js';
import GroupService from '../services/group.js';
import AbstractProductService from '../services/abstractproduct.js';
import ProductService from '../services/product.js';
import CategoryService from '../services/category.js';
import log from '../utils/log.js';
import statuses from '../utils/status.js';
const TAG = "/middlewares/existance.js";
const usernameExists = async (req, res, next) => {
try {
let { username } = req.body;
let user = await UserService.getByUsername(username);
if (!user || user == statuses.not_found) return res.status(404).send("User not found");
next();
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/usernameExists: ${e}`)); }
};
const usernameDoesntExist = async (req, res, next) => {
try {
let { username } = req.body;
let user = await UserService.getByUsername(username);
if (user != undefined && user != statuses.not_found) return res.status(400).send("Such username already taken");
next();
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/usernameDoesntExist: ${e}`)); }
};
const groupExists = async (req, res, next) => {
try {
let groupId = req.params.groupId || req.body.groupId;
let groupId = Number(req.params.groupId || req.body.groupId);
if (isNaN(groupId)) throw new customError(`groupId is not a number`, responseCodes.responses.groups.id_not_found)
let group = await GroupService.getById(groupId);
if (!group || group == statuses.not_found) return res.status(404).send("Group not found");
if (!group) throw new customError(`group does not exists`, responseCodes.responses.groups.id_not_found);
next();
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/groupExists: ${e}`)) }
};
const groupDoesntExist = async (req, res, next) => {
try {
let groupId = req.params.groupId || req.body.groupId;
let group = await GroupService.getById(groupId);
if (group || group != statuses.not_found) return res.status(400).send("Such group already exists");
next();
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/groupDoesntExist: ${e}`)) }
};
const groupNameDoesntExist = async (req, res, next) => {
try {
const { groupName } = req.params;
const groupNameExists = async (req, res, next) => {
let groupName = req.params.groupName || req.body.groupName;
let group = await GroupService.getByName(groupName);
if (group) return res.status(400).send("Such group name already exists");
if (!group) throw new customError(`group does not exists`, responseCodes.responses.groups.name_not_found);
next();
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/groupNameDoesntExist: ${e}`)); }
};
const abstractProductExists = async (req, res, next) => {
try {
const { groupId, localId } = req.body;
const localIdExists = async (req, res, next) => {
let localId = req.params.localId || req.body.localId;
let result = await AbstractProductService.exists(groupId, localId);
if (!result) return res.status(404).send("Abstract product not found");
if (!localId) throw new customError(`local id is not specified`, responseCodes.responses.general.invalid_syntax);
next();
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/abstractProductExists: ${e}`)); }
};
}
const productExists = async (req, res, next) => {
try {
const { groupId, localId } = req.body;
let result = await ProductService.exists(groupId, localId);
if (!result) return res.status(404).send("Product not found");
next();
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/productExists: ${e}`)); }
};
const categoryNameDoesntExist = async (req, res, next) => {
try {
const { categoryName, localId, groupId } = req.body;
let result = await CategoryService.getByName(groupId, localId, categoryName);
if (result != statuses.not_found) return res.status(400).send("Such category name exists");
next();
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/categoryNameDoesntExist: ${e}`)); }
};
const categoryExists = async (req, res, next) => {
try {
const { localId, groupId } = req.body;
let result = await CategoryService.getById(groupId, localId);
if (!result || result == statuses.not_found) return res.status(404).send("No such category");
next();
} catch (e) { return res.status(500).send(log.unknownError(`${TAG}/categoryExists: ${e}`)); }
};
export default {
usernameExists,
usernameDoesntExist,
groupExists,
groupDoesntExist,
groupNameDoesntExist,
abstractProductExists,
productExists,
categoryNameDoesntExist,
categoryExists
};
export default { groupExists, localIdExists, groupNameExists }

View File

@@ -0,0 +1,12 @@
class customError extends Error {
constructor(message, code) {
super(message)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, customError);
}
this.code = code;
}
}
export default customError;

View File

@@ -0,0 +1,26 @@
import log from "../utils/log.js";
import customError from "./customError.js";
import responses from './responseCodes.js';
import translate from '../utils/translate.js';
const errorHandler = (err, req, res) => {
log.error(err);
console.log(err);
let language = req.headers["accept-language"];
if (err instanceof customError) {
return res.status(responses.getHTTPCode(err.code)).send(translate(language, err.code));
}
}
const tryHandler = (func) => {
return async (req, res, next) => {
try {
await func(req, res, next);
} catch (err) {
errorHandler(err, req, res);
}
}
}
export default tryHandler;

View File

@@ -0,0 +1,213 @@
const responses = {
authentication: {
not_found: "authentication not found",
invalid: "authentication invalid"
},
user: {
duplicate: "user duplicate",
invalid_syntax: "user invalid syntax",
already_in_group: "user already in group",
not_found: "user not found",
admin_leave: "admin leave"
},
usernames: {
duplicate: "username taken",
not_found: "username not found",
required: "username required"
},
passwords: {
required: "password required",
invalid: "password invalid"
},
groups: {
duplicate: "group name taken",
name_not_found: "group name not found",
id_not_found: "group id not found",
not_an_owner: "group not an owner",
not_a_member: "group not a member",
name_not_specified: "name not specified",
new_owner_not_specified: "new owner not specified"
},
abstractproducts: {
not_found: "abstract product not found",
invalid_syntax: "abstract product invalid syntax"
},
barcode: {
not_found: "barcode not found",
duplicate: "barcode duplicate",
too_long: "barcode too long",
invalid_syntax: "barcode invalid syntax"
},
abstractproductname: {
not_found: "abstract product name not found",
duplicate: "abstract product name duplicate",
too_long: "abstract product name too long",
invalid_syntax: "abstract product name invalid syntax"
},
netweight: {
too_long: "net weight too long",
invalid_syntax: "net weight invalid syntax"
},
imagehash: {
too_long: "image hash too long",
invalid_syntax: "image hash invalid syntax"
},
category: {
too_long: "category too long",
invalid_syntax: "category invalid syntax",
not_found: "category not found"
},
unit: {
too_long: "unit too long",
invalid_syntax: "unit invalid syntax"
},
products: {
not_found: "product not found",
invalid_syntax: "product invalid syntax"
},
abstractproductid: {
not_found: "abstract product id not found",
invalid_syntax: "abstract product id invalid syntax"
},
amount: {
too_long: "amount too long",
invalid_syntax: "amount invalid syntax"
},
dateofproduction: {
too_long: "date of production too long",
invalid_syntax: "date of production invalid syntax"
},
expirydate: {
too_long: "expiry date too long",
invalid_syntax: "expiry date invalid syntax"
},
categories: {
duplicate: "categories duplicate",
not_found: "categories not found"
},
general: {
ok: "ok",
invalid_syntax: "invalid syntax",
png_only: "png only",
too_long: "too long",
unknown: "unknown"
}
}
const getHTTPCode = (type) => {
switch (type) {
case responses.general.ok:
return 200
case responses.general.invalid_syntax:
return 400
case responses.general.unknown:
return 500
case responses.general.png_only:
return 400
case responses.general.too_long:
return 400
case responses.authentication.invalid:
return 403
case responses.authentication.not_found:
return 403
case responses.user.already_in_group:
return 409
case responses.user.duplicate:
return 409
case responses.user.invalid_syntax:
return 400
case responses.user.not_found:
return 404
case responses.user.admin_leave:
return 409
case responses.usernames.duplicate:
return 409
case responses.usernames.not_found:
return 404
case responses.usernames.required:
return 400
case responses.passwords.required:
return 400
case responses.passwords.invalid:
return 403
case responses.groups.id_not_found:
return 404
case responses.groups.name_not_found:
return 404
case responses.groups.duplicate:
return 409
case responses.groups.not_an_owner:
return 403
case responses.groups.not_a_member:
return 403
case responses.groups.name_not_specified:
return 400
case responses.new_owner_not_specified:
return 400
case responses.abstractproducts.not_found:
return 404
case responses.abstractproducts.invalid_syntax:
return 400
case responses.abstractproductid.invalid_syntax:
return 400
case responses.abstractproductid.not_found:
return 404
case responses.abstractproductname.duplicate:
return 409
case responses.abstractproductname.invalid_syntax:
return 400
case responses.abstractproductname.not_found:
return 404
case responses.abstractproductname.too_long:
return 400
case responses.amount.invalid_syntax:
return 400
case responses.amount.too_long:
return 409
case responses.barcode.duplicate:
return 409
case responses.barcode.invalid_syntax:
return 400
case responses.barcode.not_found:
return 404
case responses.barcode.too_long:
return 400
case responses.categories.duplicate:
return 409
case responses.categories.not_found:
return 404
case responses.category.invalid_syntax:
return 400
case responses.category.too_long:
return 400
case responses.category.not_found:
return 404
case responses.dateofproduction.invalid_syntax:
return 400
case responses.dateofproduction.too_long:
return 400
case responses.expirydate.invalid_syntax:
return 400
case responses.expirydate.too_long:
return 400
default:
return 500
}
}
export default { responses, getHTTPCode }

View File

@@ -1,11 +1,21 @@
import { Router } from 'express';
import auth from '../middlewares/auth.js';
import AbstractProductController from '../controllers/abstractproduct.js';
import multer from 'multer';
import path from 'path';
import tryHandler from '../response/errorHandler.js';
import existance from '../middlewares/existance.js';
const upload = multer(({
dest: path.join(path.resolve(path.dirname('')), "/temp")
}));
const AbstractProductRouter = new Router();
AbstractProductRouter.post('/create', auth.authenticate, existance.groupExists, auth.userIsInGroup, AbstractProductController.create);
AbstractProductRouter.post('/update', auth.authenticate, existance.groupExists, auth.userIsInGroup, existance.abstractProductExists, AbstractProductController.update);
AbstractProductRouter.post('/create', upload.single("file"), tryHandler(auth.authenticate), tryHandler(auth.userIsInGroup), tryHandler(AbstractProductController.create));
AbstractProductRouter.post('/update', upload.single("file"), tryHandler(auth.authenticate), tryHandler(auth.userIsInGroup), tryHandler(existance.localIdExists), tryHandler(AbstractProductController.update));
AbstractProductRouter.get('/:groupId/:localId', tryHandler(auth.authenticate), tryHandler(auth.userIsInGroup), tryHandler(existance.localIdExists), tryHandler(AbstractProductController.getById));
AbstractProductRouter.get('/getImage/:groupId/:localId', tryHandler(auth.authenticate), tryHandler(auth.userIsInGroup), tryHandler(existance.localIdExists), tryHandler(AbstractProductController.getImage));
AbstractProductRouter.delete('/:groupId/:localId', tryHandler(auth.authenticate), tryHandler(auth.userIsInGroup), tryHandler(existance.localIdExists), tryHandler(AbstractProductController.delete));
export default AbstractProductRouter;

View File

@@ -1,11 +1,14 @@
import { Router } from 'express';
import auth from '../middlewares/auth.js';
import existance from '../middlewares/existance.js';
import CategoryController from '../controllers/category.js';
import tryHandler from '../response/errorHandler.js';
import existance from '../middlewares/existance.js';
const CategoryRouter = new Router();
CategoryRouter.post('/create', auth.authenticate, existance.groupExists, existance.categoryNameDoesntExist, CategoryController.create);
CategoryRouter.post('/update', auth.authenticate, existance.groupExists, existance.categoryExists, CategoryController.update);
CategoryRouter.post('/create', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.userIsInGroup), tryHandler(CategoryController.create));
CategoryRouter.post('/update', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.userIsInGroup), tryHandler(existance.localIdExists), tryHandler(CategoryController.update));
CategoryRouter.get('/:groupId/:localId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.userIsInGroup), tryHandler(existance.localIdExists), tryHandler(CategoryController.getByLocalId));
CategoryRouter.delete('/:groupId/:localId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.userIsInGroup), tryHandler(existance.localIdExists), tryHandler(CategoryController.delete));
export default CategoryRouter;

View File

@@ -1,12 +1,19 @@
import { Router } from 'express';
import auth from '../middlewares/auth.js';
import GroupController from '../controllers/group.js';
import tryHandler from '../response/errorHandler.js';
import existance from '../middlewares/existance.js';
const GroupRouter = new Router();
GroupRouter.post('/create/:groupName', auth.authenticate, existance.groupNameDoesntExist, GroupController.create);
GroupRouter.post('/join/:groupId', auth.authenticate, existance.groupExists, auth.requirePassword, auth.checkGroupPassword, GroupController.join);
GroupRouter.post('/password/:groupId', auth.authenticate, existance.groupExists, auth.authorizeGroupOwner, auth.requirePassword, GroupController.updatePassword);
GroupRouter.post('/create/:groupName', tryHandler(auth.authenticate), tryHandler(GroupController.create));
GroupRouter.post('/join/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.requirePassword), tryHandler(auth.checkGroupPassword), tryHandler(GroupController.join));
GroupRouter.get('/leave/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.userIsInGroup), tryHandler(GroupController.leave));
GroupRouter.post('/password/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.authorizeGroupOwner), tryHandler(auth.requirePassword), tryHandler(GroupController.updatePassword));
GroupRouter.get('/byName/:groupName', tryHandler(auth.authenticate), tryHandler(existance.groupNameExists), tryHandler(GroupController.getByName));
GroupRouter.get('/byId/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.userIsInGroup), tryHandler(GroupController.getById));
GroupRouter.get('/getUsers/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.userIsInGroup), tryHandler(GroupController.getUsersInGroup));
GroupRouter.get('/adminId/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.userIsInGroup), tryHandler(GroupController.getAdminId));
GroupRouter.post('/rename/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.authorizeGroupOwner), tryHandler(GroupController.rename));
GroupRouter.post('/transferOwnership/:groupId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.authorizeGroupOwner), tryHandler(GroupController.transferOwnership));
export default GroupRouter;

View File

@@ -1,11 +1,14 @@
import { Router } from 'express';
import auth from '../middlewares/auth.js';
import ProductController from '../controllers/product.js'
import tryHandler from '../response/errorHandler.js';
import existance from '../middlewares/existance.js';
const ProductRouter = new Router();
ProductRouter.post('/create', auth.authenticate, existance.groupExists, auth.userIsInGroup, ProductController.create);
ProductRouter.post('/update', auth.authenticate, existance.groupExists, auth.userIsInGroup, existance.productExists, ProductController.update);
ProductRouter.post('/create', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(auth.userIsInGroup), tryHandler(ProductController.create));
ProductRouter.post('/update', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(existance.localIdExists), tryHandler(auth.userIsInGroup), tryHandler(ProductController.update));
ProductRouter.get('/:groupId/:localId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(existance.localIdExists), tryHandler(auth.userIsInGroup), tryHandler(ProductController.getByLocalId))
ProductRouter.delete('/:groupId/:localId', tryHandler(auth.authenticate), tryHandler(existance.groupExists), tryHandler(existance.localIdExists), tryHandler(auth.userIsInGroup), tryHandler(ProductController.delete))
export default ProductRouter;

View File

@@ -1,12 +1,17 @@
import { Router } from 'express';
import auth from '../middlewares/auth.js';
import existance from '../middlewares/existance.js';
import UserController from '../controllers/user.js';
import tryHandler from '../response/errorHandler.js';
const UserRouter = new Router();
UserRouter.post('/register', auth.requireUsername, auth.requirePassword, existance.usernameDoesntExist, UserController.register);
UserRouter.post('/login', auth.requireUsername, auth.requirePassword, existance.usernameExists, UserController.login);
UserRouter.get('/synchronize/:groupId', auth.authenticate, existance.groupExists, auth.userIsInGroup, UserController.synchronize);
UserRouter.post('/register', tryHandler(auth.requireUsername), tryHandler(auth.requirePassword), tryHandler(UserController.register));
UserRouter.post('/login', tryHandler(auth.requireUsername), tryHandler(auth.requirePassword), tryHandler(UserController.login));
UserRouter.get('/synchronize/:groupId', tryHandler(auth.authenticate), tryHandler(auth.userIsInGroup), tryHandler(UserController.synchronize));
UserRouter.post('/changeUsername', tryHandler(auth.authenticate), tryHandler(auth.requireUsername), tryHandler(UserController.changeUsername));
UserRouter.post('/changePassword', tryHandler(auth.authenticate), tryHandler(auth.requirePassword), tryHandler(UserController.changePassword));
UserRouter.get('/myGroups', tryHandler(auth.authenticate), tryHandler(UserController.getAllGroupsForUser));
//Maybe I should add a middleware like "auth.usersShareGroups"
UserRouter.get('/byId/:userId', tryHandler(auth.authenticate), tryHandler(UserController.getById))
export default UserRouter;

View File

@@ -1,12 +1,14 @@
import ProductService from './product.js';
import db from '../db.js';
import statuses from '../utils/status.js';
import errorHandler from '../utils/pgerrorhandler.js';
import responses from '../response/responseCodes.js';
import customError from '../response/customError.js';
class AbstractProductService {
async create(groupid, localid, barcode, name, net_weight, image_filename, category, unit) {
await db.query("INSERT INTO abstract_products (group_id, local_id, barcode, name, net_weight, image_filename, category, unit) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", [groupid, localid, barcode, name, net_weight, image_filename, category, unit])
.catch((e) => {
errorHandler(e, "abstract product")
errorHandler(e, "abstractproduct")
});
}
@@ -20,21 +22,21 @@ class AbstractProductService {
async updateName(groupId, localId, name) {
await db.query("UPDATE abstract_products SET name = $1 WHERE group_id = $2 AND local_id = $3", [name, groupId, localId])
.catch((e) => {
errorHandler(e, "name")
errorHandler(e, "abstractproductname")
});
}
async updateNetWeight(groupId, localId, net_weight) {
await db.query("UPDATE abstract_products SET net_weight = $1 WHERE group_id = $2 AND local_id = $3", [net_weight, groupId, localId]
await db.query("UPDATE abstract_products SET net_weight = $1 WHERE group_id = $2 AND local_id = $3", [net_weight, groupId, localId])
.catch((e) => {
errorHandler(e, "net weight")
}));
errorHandler(e, "netweight")
});
}
async updateImageFilename(groupId, localId, image_filename) {
await db.query("UPDATE abstract_products SET image_filename = $1 WHERE group_id = $2 AND local_id = $3", [image_filename, groupId, localId])
.catch((e) => {
errorHandler(e, "image filename")
errorHandler(e, "imagehash")
});
}
@@ -54,7 +56,7 @@ class AbstractProductService {
async getAll(groupId) {
let result = (await db.query("SELECT local_id, barcode, name, net_weight, image_filename, category, unit FROM abstract_products WHERE group_id = $1", [groupId])).rows;
if (!result) return statuses.not_found;
// if (!result) throw new customError("getAll abstract products not found", responses.responses.abstractproducts.not_found)
return result;
}
@@ -63,6 +65,25 @@ class AbstractProductService {
if (!result) return false;
return true;
}
async getByLocalId(groupId, localId) {
let result = (await db.query("SELECT * FROM abstract_products WHERE group_id = $1 AND local_id = $2", [groupId, localId]))
if (result.rowCount == 0) throw new customError(`Abstract product not found`, responses.responses.abstractproducts.not_found)
return result.rows[0]
}
async getAllFromCategory(groupId, categoryId) {
let result = (await db.query("SELECT * FROM abstract_products WHERE group_id = $1 AND category = $2", [groupId, categoryId]));
return result.rows
}
async delete(groupId, localId) {
let productsOfAbstractProduct = await ProductService.getByAbstractProductID(groupId, localId);
productsOfAbstractProduct.forEach(async (product) => {
await ProductService.delete(product.id)
});
await db.query("DELETE FROM abstract_products WHERE group_id = $1 AND local_id = $2", [groupId, localId])
}
};
export default new AbstractProductService();

View File

@@ -1,5 +1,9 @@
import ProductService from "./product.js";
import db from '../db.js';
import statuses from '../utils/status.js';
import customError from '../response/customError.js';
import responseCodes from '../response/responseCodes.js';
import AbstractProductService from "./abstractproduct.js";
class CategoryService {
async create(groupId, categoryId, name) {
@@ -11,16 +15,31 @@ class CategoryService {
}
async getById(groupId, localId) {
let result = (await db.query("SELECT * FROM categories WHERE group_id = $1 AND local_id = $2", [groupId, localId]))
if (result.rowCount == 0) return statuses.not_found;
let result = (await db.query("SELECT * FROM categories WHERE group_id = $1 AND local_id = $2", [groupId, localId]));
if (result.rowCount == 0) throw new customError(`getById categorirs not found`, responseCodes.responses.category.not_found)
return result.rows[0];
}
async getByName(groupId, localId, name) {
let result = (await db.query("SELECT * FROM categories WHERE group_id = $1 AND local_id = $2 AND name = $3", [groupId, localId, name]));
if (result.rowCount == 0) return statuses.not_found;
if (result.rowCount == 0) throw new customError(`getByName categories not found`, responseCodes.responses.category.not_found)
return result.rows[0];
}
async getAll(groupId) {
let result = (await db.query("SELECT group_id, local_id, name FROM categories WHERE group_id = $1", [groupId])).rows
return result
}
async delete(groupId, localId) {
let abstractProductInCategory = await AbstractProductService.getAllFromCategory(groupId, localId);
abstractProductInCategory.forEach(async (abstractProduct) => {
await AbstractProductService.delete(groupId, abstractProduct.local_id)
});
await db.query("DELETE FROM categories WHERE group_id = $1 AND local_id = $2", [groupId, localId]);
}
}
export default new CategoryService();

View File

@@ -1,17 +1,18 @@
import db from '../db.js';
import customError from '../response/customError.js';
import responseCodes from '../response/responseCodes.js';
import errorHandler from '../utils/pgerrorhandler.js';
import status from '../utils/status.js';
class GroupService {
async create(name, creatorId) {
let res = await db.query("INSERT INTO groups (name, admin_id) VALUES ($1, $2) RETURNING ID", [name, creatorId]).catch(errorHandler);
let res = await db.query("INSERT INTO groups (name, admin_id) VALUES ($1, $2) RETURNING ID", [name, creatorId]).catch((e) => errorHandler(e, "groups"));
return res.rows[0];
}
async getById(id) {
let res = (await db.query("SELECT * FROM groups WHERE id = $1", [id]));
if (res.rowCount == 0) return status.not_found;
if (res.rowCount == 0) throw new customError(`getById group not found`, responseCodes.responses.groups.id_not_found);
return res.rows[0];
}
@@ -28,7 +29,21 @@ class GroupService {
}
async getByName(name) {
return (await db.query("SELECT * FROM groups WHERE name = $1", [name])).rows[0];
let res = (await db.query("SELECT id FROM groups WHERE name = $1", [name]));
if (res.rowCount == 0) throw new customError(`getByName group not found`, responseCodes.responses.groups.name_not_found);
return res.rows[0];
}
async getUsersInGroup(groupId) {
return (await db.query("SELECT id FROM users WHERE $1 = ANY(groups)", [groupId])).rows.map((group) => group.id)
}
async rename(groupId, newName) {
await db.query("UPDATE groups SET name = $1 WHERE id = $2", [newName, groupId]).catch((e) => errorHandler(e, "groups"));;
}
async transferOwnership(groupId, userId) {
await db.query("UPDATE groups SET admin_id = $1 WHERE id = $2", [userId, groupId])
}
};

View File

@@ -1,19 +1,20 @@
import db from '../db.js';
import statuses from '../utils/status.js';
import errorHandler from '../utils/pgerrorhandler.js';
import customError from '../response/customError.js';
import responseCodes from '../response/responseCodes.js';
class ProductService {
async create(groupid, localid, abstract_product_id, amount, date_of_production, expiry_date) {
await db.query("INSERT INTO products (group_id, local_id, abstract_product_id, amount, date_of_production, expiry_date) VALUES ($1, $2, $3, $4, $5, $6)", [groupid, localid, abstract_product_id, amount, date_of_production, expiry_date])
await db.query("INSERT INTO products (group_id, local_id, abstract_product_id, amount, date_of_production, expiry_date) VALUES ($1, $2, $3, $4, to_timestamp($5)::date, to_timestamp($6)::date)", [groupid, localid, abstract_product_id, amount, date_of_production, expiry_date])
.catch((e) => {
errorHandler(e, "Abstract Product")
errorHandler(e, "product")
});
}
async updateAbstractProductId(groupId, localId, abstract_product_id) {
await db.query("UPDATE products SET abstract_product_id = $1 WHERE group_id = $2 AND local_id = $3", [abstract_product_id, groupId, localId])
.catch((e) => {
errorHandler(e, "abstract product id")
errorHandler(e, "abstractproductid")
});
}
@@ -26,22 +27,22 @@ class ProductService {
}
async updateDateOfProduction(groupId, localId, date_of_production) {
await db.query("UPDATE products SET date_of_production = $1 WHERE group_id = $2 AND local_id = $3", [date_of_production, groupId, localId])
await db.query("UPDATE products SET date_of_production = to_timestamp($1)::date WHERE group_id = $2 AND local_id = $3", [date_of_production, groupId, localId])
.catch((e) => {
errorHandler(e, "date of production")
errorHandler(e, "dateofproduction")
});
}
async updateExpiryDate(groupId, localId, expiry_date) {
await db.query("UPDATE products SET expiry_date = $1 WHERE group_id = $2 AND local_id = $3", [expiry_date, groupId, localId])
await db.query("UPDATE products SET expiry_date = to_timestamp($1)::date WHERE group_id = $2 AND local_id = $3", [expiry_date, groupId, localId])
.catch((e) => {
errorHandler(e, "expiry date")
errorHandler(e, "expirydate")
});
}
async getAll(groupId) {
let result = (await db.query("SELECT local_id, abstract_product_id, amount, date_of_production, expiry_date FROM products WHERE group_id = $1", [groupId])).rows;
if (!result) return statuses.not_found;
// if (!result) throw new customError(`getAll product not found`, responseCodes.responses.products.not_found);
return result;
}
@@ -50,6 +51,21 @@ class ProductService {
if (!result) return false;
return true;
}
async getByLocalId(groupId, localId) {
let result = (await db.query("SELECT * FROM products WHERE group_id = $1 AND local_id = $2", [groupId, localId]));
if (result.rowCount == 0) throw new customError(`getByLocalId product not found`, responseCodes.responses.products.not_found);
return result.rows[0];
}
async getByAbstractProductID(groupId, abstractProductID) {
let result = (await db.query("SELECT * FROM products WHERE group_id = $1 AND abstract_product_id = $2", [groupId, abstractProductID]))
return result.rows
}
async delete(ID) {
await db.query("DELETE FROM products WHERE id = $1", [ID])
}
};
export default new ProductService();

View File

@@ -1,23 +1,28 @@
import db from '../db.js'
import statuses from '../utils/status.js';
import customError from '../response/customError.js';
import responseCodes from '../response/responseCodes.js';
import bcrypt from 'bcrypt';
import errorHandler from '../utils/pgerrorhandler.js';
class UserService {
async create(username, password) {
await db.query("INSERT INTO users (username, password) VALUES ($1, $2)", [username, bcrypt.hashSync(password, 12)]).catch((e) => {
let result = (await db.query("INSERT INTO users (username, password) VALUES ($1, $2) RETURNING id", [username, bcrypt.hashSync(password, 12)]).catch((e) => {
errorHandler(e, "user");
})
return statuses.ok;
})).rows[0].id
return result
}
async getByUsername(username) {
let user = (await db.query("SELECT * FROM Users WHERE username = $1", [username])).rows;
if (user == undefined) return statuses.not_found;
return (user[0]);
let user = (await db.query("SELECT * FROM Users WHERE username = $1", [username]));
if (user.rowCount == 0) throw new customError(`getByUsername user not found`, responseCodes.responses.usernames.not_found);
return user.rows[0];
}
async getAll() {
return (await db.query("SELECT * FROM Users")).rows;
let res = await db.query("SELECT * FROM Users");
if (res.rowCount == 0) throw new customError(`getAll user not found`, responseCodes.responses.usernames.not_found);
return res.rows[0]
}
async isInGroup(userId, groupId) {
@@ -25,9 +30,30 @@ class UserService {
}
async joinGroup(userId, groupId) {
if (await (this.isInGroup(userId, groupId))) return statuses.duplicate;
await db.query("UPDATE Users SET groups = array_append(groups, $1::integer) WHERE ID = $2", [groupId, userId]);
return statuses.ok;
if (await (this.isInGroup(userId, groupId))) throw new customError(`joinGroup user already in group`, responseCodes.responses.user.already_in_group);
await db.query("UPDATE users SET groups = array_append(groups, $1::integer) WHERE ID = $2", [groupId, userId]);
}
async leaveGroup(userId, groupId) {
await db.query("UPDATE users SET groups = array_remove(groups, $1::integer) WHERE ID = $2", [groupId, userId]);
}
async changeUsername(userId, username) {
await db.query("UPDATE users SET username = $1 WHERE id = $2", [username, userId]).catch(e => errorHandler(e, "user"));
}
async changePassword(userId, password) {
await db.query("UPDATE users SET password = $1 WHERE id = $2", [bcrypt.hashSync(password, 12), userId])
}
async getAllGroupsForUser(userId) {
return (await db.query("SELECT groups FROM users WHERE id = $1", [userId])).rows[0].groups
}
async getById(userId) {
let res = (await db.query("SELECT * FROM users WHERE id = $1", [userId]))
if (res.rowCount == 0) throw new customError(`getById no user found`, responseCodes.responses.user.not_found)
return res.rows[0]
}
}

View File

@@ -6,4 +6,8 @@ const genToken = (login) => {
return jwt.sign(payload, config.secret, { expiresIn: "7d" });
};
export default genToken;
const getUserIdFromToken = (token) => {
return jwt.decode(token).login.id;
}
export default { genToken, getUserIdFromToken };

View File

@@ -2,23 +2,23 @@ import config from '../../config.json' with {type: "json"};
const debug = (text) => {
if (config.debug) console.debug(`[D] [${Date()}]: ${text}`);
}
};
const info = (text) => {
console.log(`[I] [${Date()}]: ${text}`);
}
};
const error = (text) => {
console.error(`[E] [${Date()}]: ${text}`);
}
};
const warn = (text) => {
console.warn(`[W] [${Date()}]: ${text}`);
}
};
const unknownError = (text) => {
error(text);
return "Unknown server error. Please, report to the developer";
}
};
export default { debug, info, error, warn, unknownError };

28
src/utils/notify.js Normal file
View File

@@ -0,0 +1,28 @@
import clients from '../index.js';
import jwt from 'jsonwebtoken';
import config from '../../config.json' with {type: "json"};
import log from './log.js';
const notify = (token, groupId, action, item, data) => {
let login = jwt.decode(token, config.secret).login
let userIdCurrent = login.id
let payload = {
action,
item,
groupId,
data
}
clients.forEach(client => {
if (client.currentGroup == groupId) {
let userIdFromToken = jwt.decode(client.token, config.secret).login.id
if (userIdCurrent == userIdFromToken) return;
log.debug(`Sending user with ID ${userIdCurrent} packet "${JSON.stringify(payload)}"`)
client.socket.send(JSON.stringify(payload))
}
});
}
export default notify;

View File

@@ -1,28 +1,17 @@
import statuses from "./status.js";
import customError from "../response/customError.js";
import responseCodes from "../response/responseCodes.js";
const errorHandler = (e, obj) => {
switch (e.code) {
case '23505':
throw {
status: statuses.duplicate,
message: `Such ${obj} already exists`
};
throw new customError(`Duplicate ${obj}`, responseCodes.responses[obj].duplicate)
case '22007':
throw {
status: statuses.invalid_syntax,
message: `Invalid syntax in ${obj}`
};
throw new customError(`Invalid syntax ${obj}`, responseCodes.responses.general.invalid_syntax)
case '22001':
throw {
status: statuses.invalid_syntax,
message: `Value too long (${obj})`
};
throw new customError(`Value too long ${obj}`, responseCodes.responses[obj].too_long)
default:
throw {
status: statuses.unknown,
message: `Unknown error. Please, report to the developer`,
original: e
};
console.log(e)
throw new customError(`Unknown error ${obj}`, responseCodes.responses.general.unknown)
};
};

View File

@@ -1,9 +0,0 @@
const statuses = {
ok: "ok",
duplicate: "duplicate",
not_found: "not found",
invalid_syntax: "invalid syntax",
unknown: "unknown"
}
export default statuses

9
src/utils/translate.js Normal file
View File

@@ -0,0 +1,9 @@
import fs from 'fs';
const translate = (language, code) => {
if (!language) language = "en-US"
if (!code) code = "unknown"
return JSON.parse(fs.readFileSync(`./messages/${language}/msgs.json`).toString())[code];
}
export default translate