This commit is contained in:
seb
2026-10-29 03:28:02 +01:00
parent 907ee998c0
commit bcdfba1742
14 changed files with 184 additions and 60 deletions

View File

@@ -1693,6 +1693,18 @@
* nullable: true
* syncRootArticles:
* type: boolean
* sort:
* type: integer
* description: Display order (ascending)
* default: 0
* enabled:
* type: boolean
* description: Whether terminal is active/allowed to sync
* default: true
* clientversion:
* type: string
* nullable: true
* description: Client software version reported by POS terminal
* lastseenAt:
* type: string
* nullable: true

View File

@@ -132,6 +132,12 @@ export function register(app, basePath = '') {
* syncRootArticles:
* type: boolean
* default: true
* sort:
* type: integer
* default: 0
* enabled:
* type: boolean
* default: true
* example:
* name: "Kassa 1"
* responses:
@@ -190,6 +196,11 @@ export function register(app, basePath = '') {
const syncRootArticles = req.body.syncRootArticles !== undefined
? req.body.syncRootArticles
: req.body.sync_root_articles;
const sort = req.body.sort !== undefined ? Number(req.body.sort) : undefined;
if (sort !== undefined && !Number.isInteger(sort)) {
return res.status(400).json({ error: 'sort must be an integer' });
}
const enabled = req.body.enabled !== undefined ? Boolean(req.body.enabled) : undefined;
try {
const posterminal = await db.upsert(req.user.id, {
@@ -197,6 +208,8 @@ export function register(app, basePath = '') {
pairingpin,
rootCategoryId,
syncRootArticles,
sort,
enabled,
});
res.status(200).json({ posterminal });
} catch (error) {
@@ -239,6 +252,13 @@ export function register(app, basePath = '') {
* nullable: true
* syncRootArticles:
* type: boolean
* sort:
* type: integer
* enabled:
* type: boolean
* clientversion:
* type: string
* nullable: true
* pairingpin:
* type: string
* nullable: true
@@ -312,6 +332,22 @@ export function register(app, basePath = '') {
payload.syncRootArticles = Boolean(rawSync);
}
if (req.body.sort !== undefined) {
const rawSort = Number(req.body.sort);
if (!Number.isInteger(rawSort)) {
return res.status(400).json({ error: 'sort must be an integer' });
}
payload.sort = rawSort;
}
if (req.body.enabled !== undefined) {
payload.enabled = Boolean(req.body.enabled);
}
if (req.body.clientversion !== undefined) {
payload.clientversion = req.body.clientversion ? String(req.body.clientversion).trim() : null;
}
if (req.body.pairingpin !== undefined) {
payload.pairingpin = req.body.pairingpin ? String(req.body.pairingpin).trim() : null;
}

View File

@@ -50,6 +50,11 @@ export async function touchLastseen(id) {
return store.posterminal.touchLastseen(id);
}
export async function setClientversion(id, clientversion) {
const store = await getStore();
return store.posterminal.setClientversion(id, clientversion);
}
export async function pair(id, data) {
const store = await getStore();
return store.posterminal.pair(id, data);

41
src/db/provider.js Normal file
View File

@@ -0,0 +1,41 @@
import { MssqlProvider } from 'libdashstore/providers/mssql';
import { BetterSqlite3Provider } from 'libdashstore/providers/better-sqlite3';
import env from '../config/env.js';
function getMssqlConfig() {
const { server, database, user, password, port, encrypt, trustServerCertificate } = env.mssql;
if (!server || !database || !user || !password) {
throw new Error('MSSQL is not configured (MSSQL_SERVER, MSSQL_DATABASE, MSSQL_USER, MSSQL_PASSWORD required)');
}
return {
server,
database,
user,
password,
port,
storageDir: env.storageDir,
pool: {
max: 10,
min: 0,
idleTimeoutMillis: 30_000,
},
options: {
encrypt,
trustServerCertificate,
},
};
}
/**
* Creates the configured (uninitialized) libdashstore provider from `.env`.
* @returns {MssqlProvider | BetterSqlite3Provider}
*/
export function createProvider() {
if (env.dbProvider === 'sqlite' || env.dbProvider === 'better-sqlite3') {
return new BetterSqlite3Provider(env.sqlite);
}
return new MssqlProvider(getMssqlConfig(), { storageDir: env.storageDir });
}

View File

@@ -1,7 +1,5 @@
import LibDashStore from 'libdashstore';
import { MssqlProvider } from 'libdashstore/providers/mssql';
import { BetterSqlite3Provider } from 'libdashstore/providers/better-sqlite3';
import env from '../config/env.js';
import { createProvider } from './provider.js';
import { createLogger, chalk } from '../util/logger.js';
import { clearCachedCompanyUsers, clearCachedUser } from '../api/middleware/auth.js';
import { clearDeletedCompany, setUserCompany } from '../api/sse.js';
@@ -134,34 +132,10 @@ function wrapStoreWithTiming(rawStore) {
return rawStore;
}
function getMssqlConfig() {
const { server, database, user, password, port, encrypt, trustServerCertificate } = env.mssql;
if (!server || !database || !user || !password) {
throw new Error('MSSQL is not configured (MSSQL_SERVER, MSSQL_DATABASE, MSSQL_USER, MSSQL_PASSWORD required)');
}
return {
server,
database,
user,
password,
port,
storageDir: env.storageDir,
pool: {
max: 10,
min: 0,
idleTimeoutMillis: 30_000,
},
options: {
encrypt,
trustServerCertificate,
},
};
}
/**
* Returns the initialized LibDashStore singleton.
* `provider.initialize()` applies pending schema upgrades (libdashstore
* `src/providers/<provider>/upgrade/`) before the store is used.
* @returns {Promise<InstanceType<typeof LibDashStore>>}
*/
export async function getStore() {
@@ -171,11 +145,7 @@ export async function getStore() {
if (!connecting) {
connecting = (async () => {
if (env.dbProvider === 'sqlite' || env.dbProvider === 'better-sqlite3') {
provider = new BetterSqlite3Provider(env.sqlite);
} else {
provider = new MssqlProvider(getMssqlConfig(), { storageDir: env.storageDir });
}
provider = createProvider();
await provider.initialize();
store = wrapStoreWithTiming(await new LibDashStore(provider));
return store;

View File

@@ -57,6 +57,11 @@ export async function posAuthMiddleware(req, res, next) {
return res.status(401).json({ Message: 'Authorization has been denied for this request.' });
}
if (terminal.enabled === 0 || terminal.enabled === false) {
console.log(chalk.yellow(` [posAuth] Terminal "${terminal.id}" is disabled`));
return res.status(403).json({ Message: 'Authorization has been denied for this request.' });
}
req.posterminal = terminal;
db.touchLastseen(terminal.id).catch((err) => {
console.error('Failed to touch lastseen for posterminal:', err);

View File

@@ -83,6 +83,13 @@ export function buildClientStep2(authCode, config) {
* type: string
* description: Client device name or serial identifier (default 'JTL-POS')
* example: "Kasse 1"
* - in: query
* name: clientversion
* required: false
* schema:
* type: string
* description: Optional client app version (for future POS clients during pairing)
* example: "1.10.12.0"
* responses:
* 200:
* description: Discovery or pairing successful
@@ -160,11 +167,13 @@ export async function handleClient(req, res) {
const token = terminal.token;
const strToken = String(token || crypto.randomBytes(16).toString('hex')).trim();
const pairedAt = serverTimestamp();
const clientversion = req.query.clientversion || req.query.version || req.query.clientVersion || null;
await db.pair(terminal.id, {
token: strToken,
clientserial: name,
pairedAt,
clientversion: clientversion ? String(clientversion).trim() : null,
});
if (terminal.companyId) {