@alizadefaig/pos-server 1.2.25 → 1.2.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/core/Context.js +170 -0
- package/app/core/auth/index.js +133 -0
- package/app/core/auth/license.js +45 -0
- package/app/core/auth/posMode.js +260 -0
- package/app/core/auth/sanctum.js +209 -0
- package/app/core/auth/scope.js +231 -0
- package/app/core/emitter.js +67 -0
- package/app/core/errors.js +18 -0
- package/app/core/guard.js +54 -0
- package/app/core/idempotency.js +196 -0
- package/app/core/index.js +374 -0
- package/app/core/models/Order.js +176 -0
- package/app/core/models/OrderProduct.js +249 -0
- package/app/core/repositories/EventRepository.js +51 -0
- package/app/core/repositories/OrderProductRepository.js +225 -0
- package/app/core/repositories/OrderRepository.js +274 -0
- package/app/core/repositories/OutboxRepository.js +52 -0
- package/app/core/repositories/PaymentRepository.js +43 -0
- package/app/core/repositories/PrinterRepository.js +179 -0
- package/app/core/repositories/ProductRelations.js +148 -0
- package/app/core/repositories/SnapshotRepository.js +197 -0
- package/app/core/resources/HallResource.js +54 -0
- package/app/core/resources/OrderResource.js +177 -0
- package/app/core/resources/ProductResource.js +186 -0
- package/app/core/resources/ShiftResource.js +42 -0
- package/app/core/resources/TeamResource.js +85 -0
- package/app/core/resources/casts.js +55 -0
- package/app/core/services/AuthService.js +468 -0
- package/app/core/services/InitService.js +277 -0
- package/app/core/services/KdsService.js +639 -0
- package/app/core/services/OrderProductService.js +309 -0
- package/app/core/services/OrderService.js +903 -0
- package/app/core/services/PrintService.js +715 -0
- package/app/core/services/PrinterAdminService.js +324 -0
- package/app/core/services/ReportService.js +581 -0
- package/app/core/services/ShiftService.js +199 -0
- package/app/core/services/StorageService.js +425 -0
- package/app/core/services/TelegramService.js +162 -0
- package/app/core/support/db.js +48 -0
- package/app/core/support/ids.js +9 -0
- package/app/core/support/imageData.js +135 -0
- package/app/core/support/images.js +92 -0
- package/app/core/support/lanHandoff.js +105 -0
- package/app/core/support/lang.js +98 -0
- package/app/core/support/lock.js +63 -0
- package/app/core/support/num.js +51 -0
- package/app/core/support/php.js +125 -0
- package/app/core/support/pin.js +34 -0
- package/app/core/support/receipts.js +278 -0
- package/app/core/support/rows.js +38 -0
- package/app/core/support/str.js +31 -0
- package/app/core/support/teamSettings.js +77 -0
- package/app/core/support/telegram.js +253 -0
- package/app/core/support/telegramReport.js +143 -0
- package/app/core/support/time.js +58 -0
- package/app/core/support/tz.js +80 -0
- package/app/core/support/unlock.js +113 -0
- package/app/core/timers.js +151 -0
- package/app/db/Database.js +197 -1
- package/app/db/Migrator.js +45 -0
- package/app/db/driver.js +248 -0
- package/app/db/migrations/001_snapshot.sql +214 -0
- package/app/db/migrations/002_orders.sql +190 -0
- package/app/db/migrations/003_idempotency.sql +18 -0
- package/app/db/migrations/004_shifts.sql +21 -0
- package/app/db/migrations/005_printers.sql +43 -0
- package/app/db/migrations/006_sync.sql +136 -0
- package/app/db/migrations/007_printer_mac.sql +4 -0
- package/app/db/migrations/008_kds.sql +94 -0
- package/app/db/migrations/009_product_categories.sql +20 -0
- package/app/db/migrations/010_printers_sync.sql +43 -0
- package/app/db/migrations/011_team_logos.sql +13 -0
- package/app/db/migrations/012_category_workshop.sql +4 -0
- package/app/monitor.js +185 -0
- package/app/printers/address.js +348 -0
- package/app/proxy.js +20 -3
- package/app/relay/masters.js +173 -0
- package/app/security.js +218 -0
- package/app/socket/auth.js +160 -0
- package/app/socket/request.js +87 -0
- package/app/socket/server.js +121 -19
- package/app/sync/bridge.js +162 -0
- package/app/sync/cloud.js +120 -0
- package/app/sync/config.js +185 -0
- package/app/sync/discovery.js +546 -0
- package/app/sync/index.js +1644 -0
- package/app/sync/kv.js +26 -0
- package/app/sync/lan.js +139 -0
- package/app/sync/lanDevices.js +177 -0
- package/app/sync/lanFront.js +431 -0
- package/app/sync/mirror.js +236 -0
- package/app/sync/push.js +324 -0
- package/app/sync/reconcile.js +161 -0
- package/app/sync/signal.js +202 -0
- package/app/sync/snapshot.js +189 -0
- package/app/sync/store.js +234 -0
- package/app/tcp.js +11 -2
- package/package.json +6 -3
- package/routes/common/api/auth.js +38 -0
- package/routes/common/api/kds.js +37 -0
- package/routes/common/api/orderProducts.js +81 -0
- package/routes/common/api/orders.js +176 -0
- package/routes/common/api/printersCore.js +35 -0
- package/routes/common/api/reports.js +29 -0
- package/routes/common/socket.js +15 -35
- package/routes/common/tax.js +35 -7
- package/routes/desktop/api/offline.js +146 -0
- package/routes/desktop/api/printers.js +288 -5
- package/routes/desktop/local.js +130 -0
- package/routes/server/api/login.js +37 -0
- package/routes/server/api/platform.js +31 -0
- package/routes/server/api/shifts.js +25 -0
- package/routes/server/master.js +24 -0
- package/server.js +111 -4
- package/utils.js +6 -0
- package/tests/usbPrinterTest.js +0 -20
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const {registerOfflineRoutes} = require('../../../app/core');
|
|
4
|
+
const InitService = require('../../../app/core/services/InitService');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Облачные чтения POS (NODE-MIGRATION-PLAN, этап 1): AuthController::init и справочники, которые
|
|
8
|
+
* фронт перечитывает сам. Смены — `routes/server/api/shifts.js`. Только облако (`cloudOnly`) и только `routes/server`: на кассе те же
|
|
9
|
+
* имена обслуживают офлайн-обработчики из снапшота (`routes/desktop/api/offline.js`), а в режиме
|
|
10
|
+
* `cloud` синка — облако через прокси.
|
|
11
|
+
*
|
|
12
|
+
* Переключаются по одному через CORE_EVENTS, как и остальное ядро.
|
|
13
|
+
*/
|
|
14
|
+
const CLOUD_READ = {mutation: false, cloudOnly: true};
|
|
15
|
+
|
|
16
|
+
const init = (services) => new InitService(services);
|
|
17
|
+
|
|
18
|
+
registerOfflineRoutes(router, '/api', {
|
|
19
|
+
/** AuthController::init. `user` в ответ не входит: токен кладётся в модель и не выводится. */
|
|
20
|
+
'app.init': [CLOUD_READ, async (services) => init(services).init()],
|
|
21
|
+
|
|
22
|
+
/** ProductController::get / OrderProductController::get — оба ProductService::allProducts. */
|
|
23
|
+
'products.get': [CLOUD_READ, async (services) => init(services).products(services.context.teamId)],
|
|
24
|
+
'orderProducts.get': [CLOUD_READ, async (services) => init(services).products(services.context.teamId)],
|
|
25
|
+
|
|
26
|
+
/** ProductController::getExtraProducts / OrderProductController::getExtraProducts. */
|
|
27
|
+
'products.getExtraProducts': [CLOUD_READ, async (services) => init(services).extraProducts(services.context.teamId)],
|
|
28
|
+
'orderProducts.getExtraProducts': [CLOUD_READ, async (services) => init(services).extraProducts(services.context.teamId)],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
module.exports = router;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const {registerOfflineRoutes} = require('../../../app/core');
|
|
4
|
+
const ShiftService = require('../../../app/core/services/ShiftService');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Кассовая смена в облаке (NODE-MIGRATION-PLAN, этап 3): ShiftController. Только облако
|
|
8
|
+
* (`cloudOnly`): на кассе те же имена обслуживают офлайн-обработчики
|
|
9
|
+
* (`routes/desktop/api/offline.js`) тем же ShiftService.
|
|
10
|
+
*
|
|
11
|
+
* Закрытие смены в облаке отправляет отчёт в Telegram команды, как Laravel, но после ответа.
|
|
12
|
+
* Переключаются по одному через CORE_EVENTS, как и остальное ядро.
|
|
13
|
+
*/
|
|
14
|
+
const CLOUD_READ = {mutation: false, cloudOnly: true};
|
|
15
|
+
const CLOUD_WRITE = {mutation: true, cloudOnly: true};
|
|
16
|
+
|
|
17
|
+
const shifts = (services) => new ShiftService(services);
|
|
18
|
+
|
|
19
|
+
registerOfflineRoutes(router, '/api', {
|
|
20
|
+
'shifts.getCashShifts': [CLOUD_READ, async (services) => shifts(services).cashShifts()],
|
|
21
|
+
'shifts.openCashShift': [CLOUD_WRITE, async (services, params) => shifts(services).openCashShift(params)],
|
|
22
|
+
'shifts.closeCashShift': [CLOUD_WRITE, async (services, params) => shifts(services).closeCashShift(params)],
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
module.exports = router;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const syncConfig = require('../../app/sync/config');
|
|
3
|
+
const {relayRequest} = require('../../app/relay/masters');
|
|
4
|
+
|
|
5
|
+
const router = express.Router();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Модель M: Laravel (LocalModeMiddleware) → мастер-касса заведения. Путь служебный:
|
|
9
|
+
* при заданном INTERNAL_API_KEY его закрывает internalGuard (app/security.js).
|
|
10
|
+
* Флаг MASTER_RELAY_ENABLED выключен → 404, Laravel отвечает клиенту 503 master_unavailable.
|
|
11
|
+
*/
|
|
12
|
+
router.post('/master/request', async (req, res) => {
|
|
13
|
+
if (!syncConfig.isMasterRelayEnabled()) {
|
|
14
|
+
return res.status(404).json({relayed: false, error_code: 'relay_disabled'});
|
|
15
|
+
}
|
|
16
|
+
const body = req.body || {};
|
|
17
|
+
if (!body.company_id || !body.master_terminal_id || !body.event) {
|
|
18
|
+
return res.status(422).json({relayed: false, error_code: 'invalid_request'});
|
|
19
|
+
}
|
|
20
|
+
const {status, body: response} = await relayRequest(body);
|
|
21
|
+
res.status(status).json(response);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
module.exports = router;
|
package/server.js
CHANGED
|
@@ -7,16 +7,101 @@ const {join} = require("node:path");
|
|
|
7
7
|
const {loadRoutes, streamToString, getIpAddress} = require("./utils");
|
|
8
8
|
const {getConfig, setConfig} = require("./app/config");
|
|
9
9
|
const {loadProxy} = require("./app/proxy");
|
|
10
|
+
const {isOfflineRoute} = require("./app/core");
|
|
11
|
+
const {httpGuard} = require("./app/sync/lan");
|
|
12
|
+
const {internalGuard, originGuard} = require("./app/security");
|
|
13
|
+
const {isSyncEnabled} = require("./app/sync/config");
|
|
10
14
|
const multer = require('multer');
|
|
11
15
|
const {getLogger} = require("./app/logger/Log");
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
// Тело парсится только у локальных путей (см. ниже), но и там 500 МБ в памяти — готовый DoS:
|
|
21
|
+
// реальные тела (чек растром, пачка чеков) — единицы мегабайт. BODY_LIMIT переопределяет.
|
|
22
|
+
const BODY_LIMIT = process.env.BODY_LIMIT || '50mb';
|
|
23
|
+
const upload = multer({
|
|
24
|
+
limits: {
|
|
25
|
+
fileSize: Number(process.env.UPLOAD_FILE_LIMIT_BYTES) || 50 * 1024 * 1024,
|
|
26
|
+
files: Number(process.env.UPLOAD_MAX_FILES) || 20,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Локальные роуты Node, которым нужно РАСПАРСЕННОЕ тело. Всё остальное проксируется в апстрим
|
|
31
|
+
// и должно стримиться СЫРЫМ потоком: если буферизовать тело body-parser'ом, а затем пересобирать
|
|
32
|
+
// его через fixRequestBody, то под chunked-трафиком Cloudflare/Traefik фрейминг ломается
|
|
33
|
+
// (Transfer-Encoding: chunked + добавленный Content-Length одновременно) → nginx апстрима рубит
|
|
34
|
+
// запрос голой 400 Bad Request. Сырой стрим проходит в апстрим как есть — без пересборки.
|
|
35
|
+
const LOCAL_BODY_ROUTES = new Set([
|
|
36
|
+
'/health',
|
|
37
|
+
'/init',
|
|
38
|
+
'/socket',
|
|
39
|
+
'/socket/request',
|
|
40
|
+
'/api/printers.get',
|
|
41
|
+
'/api/printers.getUsbPrinters',
|
|
42
|
+
'/api/printers.print',
|
|
43
|
+
// офлайн-контур кассы (routes/desktop/local.js)
|
|
44
|
+
'/local/status',
|
|
45
|
+
'/local/session',
|
|
46
|
+
'/local/logout',
|
|
47
|
+
'/local/resume',
|
|
48
|
+
'/local/whoami',
|
|
49
|
+
'/local/lan',
|
|
50
|
+
// облачный Node, модель M: Laravel → мастер-касса (routes/server/master.js)
|
|
51
|
+
'/master/request',
|
|
52
|
+
]);
|
|
53
|
+
const isLocalBodyRoute = (req) => {
|
|
54
|
+
const p = req.path;
|
|
55
|
+
if (LOCAL_BODY_ROUTES.has(p)) return true;
|
|
56
|
+
// Офлайн-ядро: событие обслуживается локально ТОЛЬКО при включённом OFFLINE_CORE.
|
|
57
|
+
// Флаг выключен → путь остаётся проксируемым и тело не буферизуется, как раньше.
|
|
58
|
+
if (isOfflineRoute(p)) {
|
|
59
|
+
// Запоминаем решение: роут ядра обязан принять то же (см. registerOfflineRoutes).
|
|
60
|
+
req.coreLocal = true;
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
if (p.startsWith('/api/')) req.coreLocal = false;
|
|
64
|
+
// tax-прокси достаёт целевой URL из тела запроса (req.body.url) — ему тело нужно распарсенным
|
|
65
|
+
return p === '/tax' || p.startsWith('/tax/');
|
|
66
|
+
};
|
|
67
|
+
const bodyParsers = [
|
|
68
|
+
express.json({limit: BODY_LIMIT}),
|
|
69
|
+
express.urlencoded({extended: true, limit: BODY_LIMIT}),
|
|
70
|
+
upload.any(),
|
|
71
|
+
];
|
|
72
|
+
// Мастер раздаёт фронт телефонам зала (LAN_FRONT): страница и её файлы открыты без ключа и
|
|
73
|
+
// токена — данных в них нет, а API за ними проверяется ниже. No-op без флага.
|
|
74
|
+
app.use(require('./app/sync/lanFront').middleware());
|
|
75
|
+
// Мастер зала слушает 0.0.0.0: чужой адрес без ключа зала отсекается до любой обработки.
|
|
76
|
+
// No-op, пока синк выключен или ключ не задан.
|
|
77
|
+
app.use(httpGuard);
|
|
78
|
+
// Служебные пути Laravel → Node (/socket*, /master/*) — только с INTERNAL_API_KEY. No-op без ключа.
|
|
79
|
+
app.use(internalGuard);
|
|
80
|
+
// Касса: запросы из чужих страниц (Origin не из списка) — 403. No-op на облаке и без офлайна.
|
|
81
|
+
app.use(originGuard);
|
|
82
|
+
// Фронт открыт с https-домена, а ходит на localhost: Chromium спрашивает разрешение на запрос
|
|
83
|
+
// в приватную сеть (Private Network Access). Отвечаем только при включённом офлайн-контуре.
|
|
84
|
+
app.use((req, res, next) => {
|
|
85
|
+
if (req.headers['access-control-request-private-network'] && isSyncEnabled()) {
|
|
86
|
+
res.setHeader('Access-Control-Allow-Private-Network', 'true');
|
|
87
|
+
}
|
|
88
|
+
next();
|
|
89
|
+
});
|
|
90
|
+
app.use((req, res, next) => {
|
|
91
|
+
// Проксируемые запросы НЕ буферизуем — отдаём сырой поток дальше (в http-proxy-middleware).
|
|
92
|
+
if (!isLocalBodyRoute(req)) {
|
|
93
|
+
return next();
|
|
94
|
+
}
|
|
95
|
+
// Локальные роуты — прогоняем парсеры тела последовательно, как раньше.
|
|
96
|
+
let index = 0;
|
|
97
|
+
const run = (err) => {
|
|
98
|
+
if (err) return next(err);
|
|
99
|
+
const middleware = bodyParsers[index++];
|
|
100
|
+
if (!middleware) return next();
|
|
101
|
+
middleware(req, res, run);
|
|
102
|
+
};
|
|
103
|
+
run();
|
|
104
|
+
});
|
|
20
105
|
app.use(cors({
|
|
21
106
|
origin: '*',
|
|
22
107
|
allowedHeaders: '*',
|
|
@@ -80,6 +165,13 @@ const runExpress = (conf = null) => {
|
|
|
80
165
|
// ==== Load Common Routes ====
|
|
81
166
|
loadRoutes(app, join(__dirname, ('routes/common')));
|
|
82
167
|
|
|
168
|
+
// ==== Офлайн-ядро: тик повременных позиций (no-op при выключенном OFFLINE_CORE) ====
|
|
169
|
+
require('./app/core/timers').startTimerTicker();
|
|
170
|
+
|
|
171
|
+
// ==== Офлайн-контур (модель F): зеркало облака и досылка офлайн-правок ====
|
|
172
|
+
// No-op без OFFLINE_CORE + OFFLINE_SYNC + CORE_DB=sqlite.
|
|
173
|
+
require('./app/sync').start();
|
|
174
|
+
|
|
83
175
|
// ==== Load Proxy ====
|
|
84
176
|
loadProxy(app);
|
|
85
177
|
|
|
@@ -97,3 +189,18 @@ const runExpress = (conf = null) => {
|
|
|
97
189
|
}
|
|
98
190
|
|
|
99
191
|
module.exports = runExpress
|
|
192
|
+
|
|
193
|
+
// `npm start` / `node server.js` — самостоятельный запуск (стенд облака из ветки, без npm-пакета).
|
|
194
|
+
// Параметры из env — те же, что у обёртки cafepos-node-api. При require() ничего не запускается.
|
|
195
|
+
if (require.main === module) {
|
|
196
|
+
const flag = (value) => value === 'true';
|
|
197
|
+
runExpress({
|
|
198
|
+
isServer: flag(process.env.IS_SERVER),
|
|
199
|
+
apiUrl: process.env.PROXY || 'https://admin.cafepos.az',
|
|
200
|
+
host: process.env.APP_HOST || '0.0.0.0',
|
|
201
|
+
proxyMode: process.env.PROXY_MODE || 'axios',
|
|
202
|
+
changeProxyOrigin: flag(process.env.CHANGE_PROXY_ORIGIN),
|
|
203
|
+
port: process.env.PORT || 3005,
|
|
204
|
+
logger: flag(process.env.ENABLE_LOGGER),
|
|
205
|
+
});
|
|
206
|
+
}
|
package/utils.js
CHANGED
|
@@ -97,6 +97,12 @@ const socketSendByFilter = ({companyId, eventName, data, to, toId, excepts}, ser
|
|
|
97
97
|
serverSocket = require('./app/socket/server')();
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// Суперадминка сменила режим заведения: облачное ядро держит его в кэше (POS_MODE_CACHE_TTL),
|
|
101
|
+
// сбрасываем сразу — через TCP и /socket этот бродкаст приходит только сюда.
|
|
102
|
+
if (eventName === 'pos:mode' && companyId) {
|
|
103
|
+
require('./app/core/auth/posMode').forget(companyId);
|
|
104
|
+
}
|
|
105
|
+
|
|
100
106
|
if (!to) {
|
|
101
107
|
serverSocket.to(companyId).emit(eventName, data);
|
|
102
108
|
} else if (to === 'terminal' && toId) {
|
package/tests/usbPrinterTest.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
const escpos = require("escpos");
|
|
2
|
-
escpos.USB = require('@alizadefaig/cafetap-escpos-usb');
|
|
3
|
-
device = new escpos.USB(8137, 8214);
|
|
4
|
-
console.log(device);
|
|
5
|
-
// console.log(base64Str);
|
|
6
|
-
|
|
7
|
-
const printer = new escpos.Printer(device, {});
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
device.open((err) => {
|
|
13
|
-
console.log('asd')
|
|
14
|
-
if (err) {
|
|
15
|
-
console.log(err)
|
|
16
|
-
}
|
|
17
|
-
printer.text('Hello World!');
|
|
18
|
-
|
|
19
|
-
printer.newLine().newLine().cut().close();
|
|
20
|
-
});
|