providerrefactor part1
This commit is contained in:
44
TODO.md
44
TODO.md
@@ -1,7 +1,8 @@
|
||||
# TODO — Database provider reorganization
|
||||
|
||||
Milestones 1–3 are done (provider contract, MSSQL provider, repo reorg).
|
||||
Everything below is outstanding.
|
||||
SQLite provider, native schema, and query translations now exist; store wiring,
|
||||
migrations, lifecycle methods, and parity tests remain outstanding.
|
||||
|
||||
## 1. Convert store modules to the provider API (per area, keep behavior)
|
||||
Replace raw `pool` usage in `src/store/**` with `provider.query()` /
|
||||
@@ -12,11 +13,14 @@ For every module:
|
||||
- [ ] `pool.transaction(); begin; commit; catch rollback` → `provider.transaction(async (tx) => { ... })`
|
||||
- [ ] `result.recordset` → `result.rows`; `result.rowsAffected[0]` → `result.rowsAffected`
|
||||
- [ ] `request.js` helper (`(tx ?? pool).request()`) becomes obsolete — delete when unused
|
||||
- [x] `src/store/runner.js` bridges mixed call sites: converted modules accept a
|
||||
provider runner *or* a legacy mssql Transaction/`pool` (wrapped on the fly)
|
||||
so partially converted callers keep working
|
||||
- [ ] `recordset[0].id` after `OUTPUT INSERTED.id` → keep, but prefer `insertId` where trivial
|
||||
|
||||
Conversion order (bottom-up by dependency):
|
||||
- [ ] `lang`, `tax` (leaf helpers, used by company create)
|
||||
- [ ] `google_auth`
|
||||
- [x] `lang`, `tax` (leaf helpers, used by company create)
|
||||
- [x] `google_auth`
|
||||
- [ ] `companyuser` (resolveCompanyAccess / resolveSelectedCompanyAccess first)
|
||||
- [ ] `company`, `companysetting`
|
||||
- [ ] `customergroup`, `entity`, `entityaddress`, `entitycontact`, `entityhistory`
|
||||
@@ -49,7 +53,7 @@ Conversion order (bottom-up by dependency):
|
||||
- [ ] Document provider contract (JSDoc typedef in `src/providers/provider.js`).
|
||||
|
||||
## 4. better-sqlite3 provider
|
||||
- [ ] Add `src/providers/better-sqlite3/index.js` implementing the same contract:
|
||||
- [x] Add `src/providers/better-sqlite3/index.js` implementing the same contract:
|
||||
- `query()` → prepare + `.all()`/`.get()`/`.run()`; normalize
|
||||
`lastInsertRowid` → `insertId`, `changes` → `rowsAffected`
|
||||
- `transaction(fn)` → `db.transaction()` wrapper (note: better-sqlite3 is sync;
|
||||
@@ -60,11 +64,11 @@ Conversion order (bottom-up by dependency):
|
||||
- `BLOB` handling: return `Buffer` for pic/image/attachment binaries
|
||||
- Error classification: unique → `SQLITE_CONSTRAINT_UNIQUE`, FK → `SQLITE_CONSTRAINT_FOREIGNKEY`
|
||||
- Do NOT close the caller-owned db in the provider; expose `close()`
|
||||
- [ ] `better-sqlite3` as peerDependency (optional) in `package.json`
|
||||
- [ ] package.json exports: `"./providers/better-sqlite3"`
|
||||
- [x] `better-sqlite3` as peerDependency (optional) in `package.json`
|
||||
- [x] package.json exports: `"./providers/better-sqlite3"`
|
||||
|
||||
## 5. SQLite schema (native, not mechanically translated)
|
||||
- [ ] `src/providers/better-sqlite3/schema.js` + migrations:
|
||||
- [x] Native schema in `src/providers/sqlite3/schema.js`:
|
||||
- `INT IDENTITY` → `INTEGER PRIMARY KEY` (rowid)
|
||||
- `NVARCHAR(n)` → `TEXT`; `VARBINARY(MAX)` → `BLOB`
|
||||
- `DATETIME2 DEFAULT SYSUTCDATETIME()` → `TEXT` ISO-8601 UTC
|
||||
@@ -73,11 +77,13 @@ Conversion order (bottom-up by dependency):
|
||||
- `OUTPUT INSERTED...` in queries → `RETURNING ...`
|
||||
- `MERGE companysetting` → `INSERT ... ON CONFLICT ... DO UPDATE`
|
||||
- `[identifier]` brackets → rename or quote (`"number"`)
|
||||
- [ ] **Entity number uniqueness** (MSSQL uses indexed view `v_entity_company_number`):
|
||||
- Add `company_id` column to `entity` (denormalized from customergroup, kept
|
||||
- [ ] Add versioned SQLite migrations around the native schema
|
||||
- [ ] **Entity number uniqueness parity** (MSSQL still uses indexed view
|
||||
`v_entity_company_number`):
|
||||
- [x] SQLite: add `company_id` to `entity` (denormalized from customergroup, kept
|
||||
in sync on insert + customergroup moves) and enforce
|
||||
`CREATE UNIQUE INDEX uq_entity_company_number ON entity(company_id, number) WHERE number IS NOT NULL`
|
||||
- Add the equivalent MSSQL migration for parity; remove the indexed view
|
||||
- [ ] Add the equivalent MSSQL migration for parity; remove the indexed view
|
||||
- [ ] Versioned migrations via `meta` table (already exists) instead of pure
|
||||
`IF NOT EXISTS` scripts for both providers
|
||||
- [ ] Implement `initialize()` (migrations) and `purge()` (drop tables, FK-order)
|
||||
@@ -85,17 +91,21 @@ Conversion order (bottom-up by dependency):
|
||||
drop order — replicate)
|
||||
|
||||
## 6. SQL translation per area (SQLite queries)
|
||||
- [ ] `TOP 1` → `LIMIT 1` (mind: TOP without ORDER BY — keep identical ordering)
|
||||
- [ ] `OUTER APPLY (SELECT TOP 1 ...)` (article/category first-pic joins) →
|
||||
- [x] Add all 32 modules under `src/providers/sqlite3/queries/`, preserving the
|
||||
MSSQL query modules' named-export API (plus SQLite number helpers)
|
||||
- [x] `TOP 1` → `LIMIT 1` (mind: TOP without ORDER BY — keep identical ordering)
|
||||
- [x] `OUTER APPLY (SELECT TOP 1 ...)` (article/category first-pic joins) →
|
||||
correlated subquery or `JOIN` on min(sort,id)
|
||||
- [ ] `FOR JSON PATH` (getBySku, listSearchIndex, listExport, getTreeForCompany) →
|
||||
build JSON in JS from plain rows (preferred: keeps providers dumb)
|
||||
- [ ] Recursive CTEs (`SEARCH_ARTICLES`, `RESOLVE_ARTICLE_ACCESS_BY_SKU`,
|
||||
- [x] `FOR JSON PATH` queries → SQLite JSON1 aggregation with shapes compatible
|
||||
with the current store parsers (building JSON in JS remains a future cleanup)
|
||||
- [x] Recursive CTEs (`SEARCH_ARTICLES`, `RESOLVE_ARTICLE_ACCESS_BY_SKU`,
|
||||
`GET_CATEGORY_TREE_FLAT`) → SQLite `WITH RECURSIVE` (mostly compatible)
|
||||
- [ ] `ISNULL` / `COALESCE` differences; `FLOAT` vs `REAL`
|
||||
- [ ] Case-insensitive comparisons relying on MSSQL default collation
|
||||
- [x] `ISNULL` / `COALESCE` differences; `FLOAT` vs `REAL`
|
||||
- [x] Case-insensitive comparisons relying on MSSQL default collation
|
||||
(e.g. name-uniqueness checks) → `COLLATE NOCASE` in SQLite schema or
|
||||
explicit `LOWER()` in both dialects
|
||||
- [x] Compile all 212 complete SQLite query exports against an in-memory native
|
||||
schema (zero syntax/schema errors)
|
||||
|
||||
## 7. Contract / parity tests
|
||||
- [ ] Test harness that runs the identical suite against both providers
|
||||
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -21,7 +21,13 @@
|
||||
"mssql": "^12.7.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"better-sqlite3": "^12.0.0",
|
||||
"mssql": "^12.7.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"better-sqlite3": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@azure-rest/core-client": {
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
"exports": {
|
||||
".": "./src/index.js",
|
||||
"./schema": "./src/providers/mssql/schema.js",
|
||||
"./providers/better-sqlite3": "./src/providers/sqlite3/index.js",
|
||||
"./providers/sqlite3": "./src/providers/sqlite3/index.js",
|
||||
"./providers/sqlite3/schema": "./src/providers/sqlite3/schema.js",
|
||||
"./providers/mssql": "./src/providers/mssql/index.js",
|
||||
"./providers/provider": "./src/providers/provider.js"
|
||||
},
|
||||
@@ -29,8 +32,14 @@
|
||||
"thumbhash": "^0.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"better-sqlite3": "^12.0.0",
|
||||
"mssql": "^12.7.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"better-sqlite3": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"mssql": "^12.7.2"
|
||||
}
|
||||
|
||||
32
src/index.js
32
src/index.js
@@ -190,18 +190,18 @@ export default class LibDashStore {
|
||||
};
|
||||
|
||||
this.lang = {
|
||||
create: langCreate(pool),
|
||||
getByIso639: langGetByIso639(pool),
|
||||
ensure: langEnsure(pool),
|
||||
delete: langDelete(pool),
|
||||
list: langList(pool),
|
||||
create: langCreate(provider),
|
||||
getByIso639: langGetByIso639(provider),
|
||||
ensure: langEnsure(provider),
|
||||
delete: langDelete(provider),
|
||||
list: langList(provider),
|
||||
};
|
||||
|
||||
this.tax = {
|
||||
create: taxCreate(pool),
|
||||
getByRate: taxGetByRate(pool),
|
||||
ensure: taxEnsure(pool),
|
||||
list: taxList(pool),
|
||||
create: taxCreate(provider),
|
||||
getByRate: taxGetByRate(provider),
|
||||
ensure: taxEnsure(provider),
|
||||
list: taxList(provider),
|
||||
};
|
||||
|
||||
this.unit = {
|
||||
@@ -307,13 +307,13 @@ export default class LibDashStore {
|
||||
};
|
||||
|
||||
this.google_auth = {
|
||||
add: googleAuthAdd(pool),
|
||||
getByEmail: googleAuthGetByEmail(pool),
|
||||
getById: googleAuthGetById(pool),
|
||||
listAllowedEmails: googleAuthListAllowedEmails(pool),
|
||||
removeAllowedEmail: googleAuthRemoveAllowedEmail(pool),
|
||||
setSelectedCompany: googleAuthSetSelectedCompany(pool),
|
||||
setProfile: googleAuthSetProfile(pool),
|
||||
add: googleAuthAdd(provider),
|
||||
getByEmail: googleAuthGetByEmail(provider),
|
||||
getById: googleAuthGetById(provider),
|
||||
listAllowedEmails: googleAuthListAllowedEmails(provider),
|
||||
removeAllowedEmail: googleAuthRemoveAllowedEmail(provider),
|
||||
setSelectedCompany: googleAuthSetSelectedCompany(provider),
|
||||
setProfile: googleAuthSetProfile(provider),
|
||||
};
|
||||
|
||||
this.pic = {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* { rows, rowsAffected, insertId }
|
||||
*/
|
||||
|
||||
import sql from 'mssql';
|
||||
|
||||
function normalizeRows(result) {
|
||||
const recordset = result?.recordset ?? [];
|
||||
const affected = Array.isArray(result?.rowsAffected)
|
||||
@@ -55,24 +57,67 @@ export class MssqlExecutor {
|
||||
}
|
||||
|
||||
export class MssqlProvider {
|
||||
/** @param {import('mssql').ConnectionPool} pool */
|
||||
constructor(pool) {
|
||||
if (!pool || typeof pool.request !== 'function') {
|
||||
throw new TypeError('MssqlProvider requires an mssql ConnectionPool');
|
||||
/**
|
||||
* @param {import('mssql').ConnectionPool | import('mssql').config} poolOrConfig
|
||||
* Either an already-connected mssql ConnectionPool, or a plain mssql
|
||||
* config object — in which case the pool is created lazily on first use
|
||||
* (and closed by {@link MssqlProvider#close}).
|
||||
*/
|
||||
constructor(poolOrConfig) {
|
||||
const isPool =
|
||||
poolOrConfig && typeof poolOrConfig.request === 'function';
|
||||
if (!poolOrConfig || (!isPool && !poolOrConfig.server)) {
|
||||
throw new TypeError(
|
||||
'MssqlProvider requires an mssql ConnectionPool or a config object with `server`',
|
||||
);
|
||||
}
|
||||
this.pool = pool;
|
||||
// Keep a stable pool object available immediately. Transitional store
|
||||
// modules capture `provider.pool` in their factory closures, so assigning
|
||||
// a different object after a lazy connection would leave them with null.
|
||||
this.pool = isPool ? poolOrConfig : new sql.ConnectionPool(poolOrConfig);
|
||||
this.#config = isPool ? null : poolOrConfig;
|
||||
this.dialect = 'mssql';
|
||||
}
|
||||
|
||||
#config;
|
||||
#connecting;
|
||||
|
||||
/**
|
||||
* Returns the underlying pool, creating and connecting it on first use
|
||||
* when the provider was constructed from a config object.
|
||||
* @returns {Promise<import('mssql').ConnectionPool>}
|
||||
*/
|
||||
ensurePool() {
|
||||
if (this.pool.connected) return Promise.resolve(this.pool);
|
||||
if (!this.#connecting) {
|
||||
this.#connecting = this.pool
|
||||
.connect()
|
||||
.then((pool) => {
|
||||
return pool;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.#connecting = undefined;
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
return this.#connecting;
|
||||
}
|
||||
|
||||
makeExecutor(requestFactory = () => this.pool.request()) {
|
||||
return new MssqlExecutor(requestFactory);
|
||||
}
|
||||
|
||||
async #withPool(fn) {
|
||||
const pool = await this.ensurePool();
|
||||
return fn(new MssqlExecutor(() => pool.request()));
|
||||
}
|
||||
|
||||
async query(sql, params = {}) {
|
||||
return this.makeExecutor().query(sql, params);
|
||||
return this.#withPool((executor) => executor.query(sql, params));
|
||||
}
|
||||
|
||||
async exec(procName, options) {
|
||||
return this.makeExecutor().exec(procName, options);
|
||||
return this.#withPool((executor) => executor.exec(procName, options));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +129,8 @@ export class MssqlProvider {
|
||||
* @returns {Promise<T>}
|
||||
*/
|
||||
async transaction(fn) {
|
||||
const tx = this.pool.transaction();
|
||||
const pool = await this.ensurePool();
|
||||
const tx = pool.transaction();
|
||||
await tx.begin();
|
||||
try {
|
||||
const runner = this.makeExecutor(() => tx.request());
|
||||
@@ -111,7 +157,7 @@ export class MssqlProvider {
|
||||
}
|
||||
|
||||
close() {
|
||||
return this.pool.close();
|
||||
return this.pool ? this.pool.close() : Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
134
src/providers/sqlite3/index.js
Normal file
134
src/providers/sqlite3/index.js
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Provider for a caller-owned `better-sqlite3` Database instance.
|
||||
*
|
||||
* The driver is synchronous, but this adapter exposes the asynchronous
|
||||
* provider contract used by LibDashStore.
|
||||
*/
|
||||
|
||||
function bindParams(params) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(params).map(([key, value]) => [
|
||||
key.replace(/^[@:$]/, ''),
|
||||
value === undefined ? null : value,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeInsertId(value) {
|
||||
if (value === undefined || value === null) return null;
|
||||
if (typeof value !== 'bigint') return value;
|
||||
return value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : value;
|
||||
}
|
||||
|
||||
export class BetterSqlite3Executor {
|
||||
/** @param {import('better-sqlite3').Database} db */
|
||||
constructor(db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
/** @returns {Promise<import('../provider.js').QueryResult>} */
|
||||
async query(sql, params = {}) {
|
||||
const statement = this.db.prepare(sql);
|
||||
const bindings = bindParams(params);
|
||||
|
||||
if (statement.reader) {
|
||||
const rows = statement.all(bindings);
|
||||
return {
|
||||
rows,
|
||||
rowsAffected: rows.length,
|
||||
insertId: normalizeInsertId(rows[0]?.id),
|
||||
};
|
||||
}
|
||||
|
||||
const result = statement.run(bindings);
|
||||
return {
|
||||
rows: [],
|
||||
rowsAffected: result.changes,
|
||||
insertId: normalizeInsertId(result.lastInsertRowid),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class BetterSqlite3Provider extends BetterSqlite3Executor {
|
||||
/**
|
||||
* @param {import('better-sqlite3').Database} db An open, caller-owned database.
|
||||
*/
|
||||
constructor(db) {
|
||||
if (
|
||||
!db ||
|
||||
typeof db.prepare !== 'function' ||
|
||||
typeof db.exec !== 'function' ||
|
||||
typeof db.pragma !== 'function'
|
||||
) {
|
||||
throw new TypeError(
|
||||
'BetterSqlite3Provider requires an open better-sqlite3 Database',
|
||||
);
|
||||
}
|
||||
|
||||
super(db);
|
||||
this.dialect = 'sqlite3';
|
||||
db.pragma('foreign_keys = ON');
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.pragma('busy_timeout = 5000');
|
||||
}
|
||||
|
||||
#transactionTail = Promise.resolve();
|
||||
|
||||
/**
|
||||
* Runs `fn(runner)` in an immediate write transaction. Transactions are
|
||||
* serialized because better-sqlite3 has one synchronous connection.
|
||||
*
|
||||
* @template T
|
||||
* @param {(runner: BetterSqlite3Executor) => Promise<T> | T} fn
|
||||
* @returns {Promise<T>}
|
||||
*/
|
||||
transaction(fn) {
|
||||
if (typeof fn !== 'function') {
|
||||
return Promise.reject(new TypeError('transaction requires a callback'));
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
this.db.exec('BEGIN IMMEDIATE');
|
||||
try {
|
||||
const result = await fn(new BetterSqlite3Executor(this.db));
|
||||
this.db.exec('COMMIT');
|
||||
return result;
|
||||
} catch (err) {
|
||||
if (this.db.inTransaction) this.db.exec('ROLLBACK');
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const pending = this.#transactionTail.then(run, run);
|
||||
this.#transactionTail = pending.catch(() => undefined);
|
||||
return pending;
|
||||
}
|
||||
|
||||
async exec() {
|
||||
throw new Error('Stored procedures are not supported by better-sqlite3');
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
// SQLite migrations are provided by the SQLite schema layer.
|
||||
}
|
||||
|
||||
async purge() {
|
||||
// SQLite schema teardown is provided by the SQLite schema layer.
|
||||
}
|
||||
|
||||
/** Explicitly closes the caller-owned database. It is never closed implicitly. */
|
||||
close() {
|
||||
this.db.close();
|
||||
}
|
||||
}
|
||||
|
||||
export function isUniqueConstraintError(err) {
|
||||
return (
|
||||
err?.code === 'SQLITE_CONSTRAINT_UNIQUE' ||
|
||||
err?.code === 'SQLITE_CONSTRAINT_PRIMARYKEY'
|
||||
);
|
||||
}
|
||||
|
||||
export function isForeignKeyConstraintError(err) {
|
||||
return err?.code === 'SQLITE_CONSTRAINT_FOREIGNKEY';
|
||||
}
|
||||
187
src/providers/sqlite3/queries/article.js
Normal file
187
src/providers/sqlite3/queries/article.js
Normal file
@@ -0,0 +1,187 @@
|
||||
import * as source from '../../mssql/queries/article.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'article');
|
||||
export const {
|
||||
ADD_CATEGORY, ADD_EAN, ARTICLE_LIST_COLUMNS, CREATE,
|
||||
DELETE_CATEGORIES_BY_ARTICLE, DELETE_EANS_BY_ARTICLE, EAN_EXISTS,
|
||||
GET_BRAND_BY_COMPANY_AND_ID,
|
||||
GET_CATEGORY_ID_BY_SKU, GET_CATEGORY_TREE_FLAT, GET_ID_BY_SKU,
|
||||
SET_FATHER_SORT,
|
||||
SET_IS_FATHER, UPDATE_BRAND, UPDATE_FATHER_BY_SKU, UPDATE_UNIT,
|
||||
UPDATE_UNITAMOUNT, UPDATE_UNIT_AND_AMOUNT, UPDATE_WEIGHT,
|
||||
} = queries;
|
||||
|
||||
export const ARTICLE_LIST_JOINS = `
|
||||
LEFT JOIN articletext at
|
||||
ON at.article_id = a.id AND at.language_id = @languageId
|
||||
LEFT JOIN price pr
|
||||
ON pr.article_id = a.id
|
||||
AND pr.customergroup_id = @posCustomerGroupId
|
||||
AND pr.minmultiplier = 1
|
||||
LEFT JOIN tax t ON t.id = pr.tax_id
|
||||
LEFT JOIN pic ON pic.id = (
|
||||
SELECT ap.pic_id FROM articlepic ap
|
||||
WHERE ap.article_id = a.id ORDER BY ap.sort, ap.id LIMIT 1
|
||||
)
|
||||
LEFT JOIN article father ON father.id = a.fatherarticle_id
|
||||
`;
|
||||
|
||||
export const RESOLVE_ARTICLE_ACCESS_BY_SKU = `
|
||||
WITH RECURSIVE article_cat AS (
|
||||
SELECT a.id AS articleId, ca.category_id AS categoryId
|
||||
FROM article a INNER JOIN categoryarticle ca ON ca.article_id = a.id
|
||||
WHERE a.sku = @sku LIMIT 1
|
||||
), ancestors AS (
|
||||
SELECT c.id, c.parent_id FROM category c
|
||||
INNER JOIN article_cat ac ON c.id = ac.categoryId
|
||||
UNION ALL
|
||||
SELECT c.id, c.parent_id FROM category c
|
||||
INNER JOIN ancestors a ON c.id = a.parent_id
|
||||
), root AS (
|
||||
SELECT id AS rootCategoryId FROM ancestors WHERE parent_id IS NULL LIMIT 1
|
||||
)
|
||||
SELECT ac.articleId, ac.categoryId, r.rootCategoryId,
|
||||
co.id AS companyId, co.default_language_id AS languageId,
|
||||
co.poscustomergroup_id AS posCustomerGroupId, cu.id AS companyuserId
|
||||
FROM article_cat ac
|
||||
LEFT JOIN root r ON 1 = 1
|
||||
LEFT JOIN company co ON co.root_category_id = r.rootCategoryId
|
||||
LEFT JOIN companyuser cu
|
||||
ON cu.company_id = co.id AND cu.google_auth_id = @googleAuthId
|
||||
`;
|
||||
|
||||
const JSON_EANS = `(SELECT COALESCE(json_group_array(json_object('ean', ean)), '[]')
|
||||
FROM (SELECT ean FROM articleean WHERE article_id = a.id ORDER BY ean))`;
|
||||
const JSON_CATEGORIES = `(SELECT COALESCE(json_group_array(json_object('category_id', category_id)), '[]')
|
||||
FROM (SELECT category_id FROM categoryarticle WHERE article_id = a.id ORDER BY category_id))`;
|
||||
|
||||
export const SEARCH_ARTICLES = `
|
||||
WITH RECURSIVE category_tree AS (
|
||||
SELECT id FROM category WHERE id = @rootCategoryId
|
||||
UNION ALL SELECT c.id FROM category c
|
||||
INNER JOIN category_tree ct ON c.parent_id = ct.id
|
||||
)
|
||||
SELECT a.id, a.sku, at.name,
|
||||
${JSON_EANS} AS eansJson,
|
||||
${JSON_CATEGORIES} AS categoryIdsJson
|
||||
FROM article a
|
||||
LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = @languageId
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM categoryarticle ca
|
||||
INNER JOIN category_tree ct ON ct.id = ca.category_id
|
||||
WHERE ca.article_id = a.id
|
||||
)
|
||||
ORDER BY a.id
|
||||
`;
|
||||
|
||||
export const GET_ARTICLE_EXPORT = `
|
||||
WITH RECURSIVE category_tree AS (
|
||||
SELECT id FROM category WHERE id = @categoryId
|
||||
UNION ALL SELECT c.id FROM category c
|
||||
INNER JOIN category_tree ct ON c.parent_id = ct.id
|
||||
)
|
||||
SELECT DISTINCT a.id, a.sku, at.name, at.slug, u.si AS unitSi,
|
||||
ut.symbol AS unitSymbol, a.unitamount, b.name AS brandName,
|
||||
pr.net, t.rate AS taxRate,
|
||||
${JSON_EANS} AS eansJson,
|
||||
${JSON_CATEGORIES} AS categoryIdsJson,
|
||||
(SELECT COALESCE(json_group_array(json_object('hash', hash)), '[]')
|
||||
FROM (SELECT p.hash FROM articlepic ap INNER JOIN pic p ON p.id = ap.pic_id
|
||||
WHERE ap.article_id = a.id ORDER BY ap.sort, ap.id)) AS fullHashesJson
|
||||
FROM article a
|
||||
INNER JOIN categoryarticle ca ON ca.article_id = a.id
|
||||
INNER JOIN category_tree ct ON ct.id = ca.category_id
|
||||
LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = @languageId
|
||||
LEFT JOIN unit u ON u.id = a.unit_id
|
||||
LEFT JOIN unittext ut ON ut.unit_id = u.id AND ut.language_id = @languageId
|
||||
LEFT JOIN brand b ON b.id = a.brand_id
|
||||
LEFT JOIN price pr ON pr.article_id = a.id
|
||||
AND pr.customergroup_id = @posCustomerGroupId AND pr.minmultiplier = 1
|
||||
LEFT JOIN tax t ON t.id = pr.tax_id
|
||||
ORDER BY a.id
|
||||
`;
|
||||
|
||||
export const GET_ARTICLE_BY_SKU_FULL = `
|
||||
WITH RECURSIVE article_cat AS (
|
||||
SELECT a.id AS articleId, ca.category_id AS categoryId
|
||||
FROM article a INNER JOIN categoryarticle ca ON ca.article_id = a.id
|
||||
WHERE a.sku = @sku LIMIT 1
|
||||
), ancestors AS (
|
||||
SELECT c.id, c.parent_id FROM category c
|
||||
INNER JOIN article_cat ac ON c.id = ac.categoryId
|
||||
UNION ALL
|
||||
SELECT c.id, c.parent_id FROM category c
|
||||
INNER JOIN ancestors a ON c.id = a.parent_id
|
||||
), root AS (
|
||||
SELECT id AS rootCategoryId FROM ancestors WHERE parent_id IS NULL LIMIT 1
|
||||
), auth AS (
|
||||
SELECT ac.articleId, ac.categoryId, r.rootCategoryId,
|
||||
co.id AS companyId, co.default_language_id AS languageId,
|
||||
co.poscustomergroup_id AS posCustomerGroupId, cu.id AS companyuserId
|
||||
FROM article_cat ac
|
||||
LEFT JOIN root r ON 1 = 1
|
||||
LEFT JOIN company co ON co.root_category_id = r.rootCategoryId
|
||||
LEFT JOIN companyuser cu
|
||||
ON cu.company_id = co.id AND cu.google_auth_id = @googleAuthId
|
||||
)
|
||||
SELECT auth.*,
|
||||
json_object(
|
||||
'id', a.id, 'sku', a.sku, 'name', at.name, 'slug', at.slug,
|
||||
'unitamount', a.unitamount, 'weight', a.weight,
|
||||
'unitSi', u.si, 'unitBasesi', u.basesi,
|
||||
'unitBasesifactor', u.basesifactor, 'unitSymbol', ut.symbol,
|
||||
'unitName', ut.name, 'brandName', b.name,
|
||||
'brandThumbW', bp.thumbw, 'brandThumbH', bp.thumbh,
|
||||
'brandThumbHash', bp.thumbhash,
|
||||
'optionsCount', (SELECT COUNT(*) FROM optionslistarticle WHERE optionslist_id = a.optionslist_id),
|
||||
'articleListCount', (SELECT COUNT(*) FROM articlelistarticle WHERE articlelist_id = a.articlelist_id),
|
||||
'childCount', (SELECT COUNT(*) FROM article WHERE fatherarticle_id = a.id),
|
||||
'fatherArticleId', a.fatherarticle_id, 'fatherSku', father.sku,
|
||||
'categoryIdsJson', json(${JSON_CATEGORIES}),
|
||||
'photosJson', json((SELECT COALESCE(json_group_array(json_object(
|
||||
'sort', sort, 'fullHash', hash, 'fullW', w, 'fullH', h,
|
||||
'fullSize', size, 'thumbW', thumbw, 'thumbH', thumbh,
|
||||
'thumbHash', thumbhash
|
||||
)), '[]') FROM (
|
||||
SELECT ap.sort, p.hash, p.w, p.h, p.size, p.thumbw, p.thumbh, p.thumbhash
|
||||
FROM articlepic ap INNER JOIN pic p ON p.id = ap.pic_id
|
||||
WHERE ap.article_id = a.id ORDER BY ap.sort, ap.id
|
||||
))),
|
||||
'eansJson', json(${JSON_EANS}),
|
||||
'pricesJson', json((SELECT COALESCE(json_group_array(json_object(
|
||||
'minmultiplier', minmultiplier, 'net', net, 'taxRate', rate,
|
||||
'customergroupId', customergroup_id
|
||||
)), '[]') FROM (
|
||||
SELECT pr.minmultiplier, pr.net, t.rate, pr.customergroup_id
|
||||
FROM price pr INNER JOIN tax t ON t.id = pr.tax_id
|
||||
WHERE pr.article_id = a.id ORDER BY pr.customergroup_id, pr.minmultiplier
|
||||
))),
|
||||
'suppliersJson', json((SELECT COALESCE(json_group_array(json_object(
|
||||
'id', id, 'articleId', article_id, 'entityId', entity_id,
|
||||
'supplierName', supplier_name, 'sku', sku, 'pu', pu,
|
||||
'stock', stock, 'sort', sort, 'priceId', price_id,
|
||||
'price', CASE WHEN price_id IS NULL THEN NULL ELSE json_object(
|
||||
'net', net, 'taxId', tax_id, 'taxRate', tax_rate
|
||||
) END
|
||||
)), '[]') FROM (
|
||||
SELECT s.id, s.article_id, s.entity_id, e.name AS supplier_name,
|
||||
s.sku, s.pu, s.stock, s.sort, s.price_id,
|
||||
pr.net, pr.tax_id, t.rate AS tax_rate
|
||||
FROM articlesupplier s INNER JOIN entity e ON e.id = s.entity_id
|
||||
LEFT JOIN price pr ON pr.id = s.price_id
|
||||
LEFT JOIN tax t ON t.id = pr.tax_id
|
||||
WHERE s.article_id = a.id ORDER BY s.sort, s.id
|
||||
)))
|
||||
) AS articleJson
|
||||
FROM auth
|
||||
INNER JOIN article a ON a.id = auth.articleId
|
||||
LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = auth.languageId
|
||||
LEFT JOIN unit u ON u.id = a.unit_id
|
||||
LEFT JOIN unittext ut ON ut.unit_id = u.id AND ut.language_id = auth.languageId
|
||||
LEFT JOIN brand b ON b.id = a.brand_id
|
||||
LEFT JOIN pic bp ON bp.id = (
|
||||
SELECT pic_id FROM brandpic WHERE brand_id = b.id ORDER BY sort, id LIMIT 1
|
||||
)
|
||||
LEFT JOIN article father ON father.id = a.fatherarticle_id
|
||||
`;
|
||||
13
src/providers/sqlite3/queries/articleDelete.js
Normal file
13
src/providers/sqlite3/queries/articleDelete.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as source from '../../mssql/queries/articleDelete.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'articleDelete');
|
||||
export const {
|
||||
DELETE_ARTICLELIST, DELETE_BY_ID, DELETE_OPTIONSLIST, GET_CHILD_COUNT,
|
||||
GET_LIST_IDS,
|
||||
} = queries;
|
||||
|
||||
export const DELETE_PICS_BY_ARTICLE = `
|
||||
DELETE FROM pic
|
||||
WHERE id IN (SELECT pic_id FROM articlepic WHERE article_id = @articleId)
|
||||
`;
|
||||
37
src/providers/sqlite3/queries/articleDetail.js
Normal file
37
src/providers/sqlite3/queries/articleDetail.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import * as source from '../../mssql/queries/articleDetail.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'articleDetail');
|
||||
export const {
|
||||
ARTICLE_DETAIL_COLUMNS, GET_CATEGORY_IDS_BY_ARTICLE_ID,
|
||||
GET_EANS_BY_ARTICLE_ID, GET_PHOTOS_BY_ARTICLE_ID, GET_PRICES_BY_ARTICLE_ID,
|
||||
} = queries;
|
||||
|
||||
const DETAIL_JOINS = `
|
||||
LEFT JOIN articletext at
|
||||
ON at.article_id = a.id AND at.language_id = @languageId
|
||||
LEFT JOIN unit u ON u.id = a.unit_id
|
||||
LEFT JOIN unittext ut
|
||||
ON ut.unit_id = u.id AND ut.language_id = @languageId
|
||||
LEFT JOIN brand b ON b.id = a.brand_id
|
||||
LEFT JOIN pic brand_pic ON brand_pic.id = (
|
||||
SELECT bp.pic_id FROM brandpic bp
|
||||
WHERE bp.brand_id = b.id
|
||||
ORDER BY bp.sort, bp.id LIMIT 1
|
||||
)
|
||||
LEFT JOIN article father ON father.id = a.fatherarticle_id
|
||||
`;
|
||||
|
||||
export const GET_DETAIL_BY_SKU = `
|
||||
SELECT ${ARTICLE_DETAIL_COLUMNS}
|
||||
FROM article a
|
||||
${DETAIL_JOINS}
|
||||
WHERE a.sku = @sku
|
||||
`;
|
||||
|
||||
export const GET_DETAIL_BY_ARTICLE_ID = `
|
||||
SELECT ${ARTICLE_DETAIL_COLUMNS}
|
||||
FROM article a
|
||||
${DETAIL_JOINS}
|
||||
WHERE a.id = @articleId
|
||||
`;
|
||||
5
src/providers/sqlite3/queries/articlehistory.js
Normal file
5
src/providers/sqlite3/queries/articlehistory.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as source from '../../mssql/queries/articlehistory.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'articlehistory');
|
||||
export const { INSERT, LIST_BY_ARTICLE_ID } = queries;
|
||||
14
src/providers/sqlite3/queries/articlepic.js
Normal file
14
src/providers/sqlite3/queries/articlepic.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import * as source from '../../mssql/queries/articlepic.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'articlepic');
|
||||
export const {
|
||||
ADD, GET_NEXT_SORT, LIST_HASHES_BY_ARTICLE, SET_TEMP_SORTS,
|
||||
} = queries;
|
||||
|
||||
export const UPDATE_SORT_BY_HASH = `
|
||||
UPDATE articlepic
|
||||
SET sort = @sort
|
||||
WHERE article_id = @articleId
|
||||
AND pic_id = (SELECT id FROM pic WHERE hash = @fullHash)
|
||||
`;
|
||||
44
src/providers/sqlite3/queries/articlesupplier.js
Normal file
44
src/providers/sqlite3/queries/articlesupplier.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import * as source from '../../mssql/queries/articlesupplier.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'articlesupplier');
|
||||
export const {
|
||||
CHECK_EXISTS, CREATE, DELETE_BY_ID_AND_ARTICLE_ID, DELETE_PRICE,
|
||||
GET_BY_ID_AND_ARTICLE_ID, GET_COMPANY_DEFAULT_TAX, GET_NEXT_SORT,
|
||||
LIST_BY_ARTICLE_ID, LIST_IDS_BY_ARTICLE,
|
||||
SET_TEMP_SORTS, UPDATE, UPDATE_PRICE, UPDATE_SORT,
|
||||
} = queries;
|
||||
|
||||
export const LIST_BY_ENTITY_AND_SKUS = `
|
||||
SELECT
|
||||
asup.id, asup.article_id AS articleId, asup.entity_id AS entityId,
|
||||
e.name AS supplierName, asup.sku, asup.pu, asup.stock, asup.sort,
|
||||
asup.price_id AS priceId, p.net AS priceNet, p.tax_id AS priceTaxId,
|
||||
t.rate AS priceTaxRate, a.id AS a_id, a.sku AS a_sku,
|
||||
at.name AS a_name, at.slug AS a_slug,
|
||||
(SELECT COALESCE(json_group_array(json_object(
|
||||
'sort', sort, 'hash', hash, 'thumbhash', thumbhash,
|
||||
'thumbw', thumbw, 'thumbh', thumbh
|
||||
)), '[]') FROM (
|
||||
SELECT ap.sort, pic.hash, pic.thumbhash, pic.thumbw, pic.thumbh
|
||||
FROM articlepic ap INNER JOIN pic ON pic.id = ap.pic_id
|
||||
WHERE ap.article_id = a.id ORDER BY ap.sort, ap.id
|
||||
)) AS photosJson,
|
||||
(SELECT COALESCE(json_group_array(json_object('ean', ean)), '[]')
|
||||
FROM (SELECT ean FROM articleean WHERE article_id = a.id ORDER BY ean)) AS eansJson
|
||||
FROM articlesupplier asup
|
||||
INNER JOIN entity e ON e.id = asup.entity_id
|
||||
INNER JOIN customergroup cg ON cg.id = e.customergroup_id
|
||||
INNER JOIN article a ON a.id = asup.article_id
|
||||
LEFT JOIN articletext at ON at.article_id = a.id AND at.language_id = @languageId
|
||||
LEFT JOIN price p ON p.id = asup.price_id
|
||||
LEFT JOIN tax t ON t.id = p.tax_id
|
||||
WHERE asup.entity_id = @entityId
|
||||
AND cg.company_id = @companyId
|
||||
AND (
|
||||
@skusJson IS NULL
|
||||
OR a.sku IN (SELECT value FROM json_each(@skusJson))
|
||||
OR asup.sku IN (SELECT value FROM json_each(@skusJson))
|
||||
)
|
||||
ORDER BY asup.sort, asup.id
|
||||
`;
|
||||
5
src/providers/sqlite3/queries/articletext.js
Normal file
5
src/providers/sqlite3/queries/articletext.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as source from '../../mssql/queries/articletext.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'articletext');
|
||||
export const { CREATE, UPDATE_BY_ARTICLE_AND_LANGUAGE } = queries;
|
||||
8
src/providers/sqlite3/queries/brand.js
Normal file
8
src/providers/sqlite3/queries/brand.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import * as source from '../../mssql/queries/brand.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'brand');
|
||||
export const {
|
||||
CREATE, DELETE_BY_COMPANY_AND_ID, GET_BY_COMPANY_AND_NAME,
|
||||
LIST_BY_COMPANY, UPDATE_BY_ID,
|
||||
} = queries;
|
||||
41
src/providers/sqlite3/queries/category.js
Normal file
41
src/providers/sqlite3/queries/category.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import * as source from '../../mssql/queries/category.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
import { ARTICLE_LIST_COLUMNS, ARTICLE_LIST_JOINS } from './article.js';
|
||||
|
||||
const queries = translateQueries(source, 'category');
|
||||
export const {
|
||||
CREATE, DELETE, GET_BY_PARENT_AND_NAME,
|
||||
GET_NEXT_SIBLING_SORT, IS_ANCESTOR, LIST_CHILD_IDS,
|
||||
MOVE, RESOLVE_CATEGORY_ACCESS, UPDATE_SORT,
|
||||
} = queries;
|
||||
|
||||
export const GET_CATEGORY_ARTICLES = `
|
||||
SELECT ${ARTICLE_LIST_COLUMNS}
|
||||
FROM categoryarticle ca
|
||||
INNER JOIN article a ON a.id = ca.article_id
|
||||
${ARTICLE_LIST_JOINS}
|
||||
WHERE ca.category_id = @categoryId
|
||||
ORDER BY a.id
|
||||
`;
|
||||
|
||||
export const GET_TREE_FOR_COMPANY = `
|
||||
WITH RECURSIVE tree AS (
|
||||
SELECT id, parent_id, sort FROM category WHERE id = @rootId
|
||||
UNION ALL
|
||||
SELECT c.id, c.parent_id, c.sort
|
||||
FROM category c INNER JOIN tree t ON c.parent_id = t.id
|
||||
)
|
||||
SELECT
|
||||
t.id, t.parent_id AS parentId, t.sort, ct.name, ct.slug,
|
||||
p.hash AS fullHash, p.thumbw AS thumbW, p.thumbh AS thumbH,
|
||||
p.thumbhash AS thumbHash
|
||||
FROM tree t
|
||||
LEFT JOIN categorytext ct
|
||||
ON ct.category_id = t.id AND ct.language_id = @languageId
|
||||
LEFT JOIN pic p ON p.id = (
|
||||
SELECT cp.pic_id FROM categorypic cp
|
||||
WHERE cp.category_id = t.id
|
||||
ORDER BY cp.sort, cp.id LIMIT 1
|
||||
)
|
||||
ORDER BY t.sort, t.id
|
||||
`;
|
||||
1
src/providers/sqlite3/queries/categoryarticle.js
Normal file
1
src/providers/sqlite3/queries/categoryarticle.js
Normal file
@@ -0,0 +1 @@
|
||||
export { ADD } from '../../mssql/queries/categoryarticle.js';
|
||||
5
src/providers/sqlite3/queries/categorypic.js
Normal file
5
src/providers/sqlite3/queries/categorypic.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as source from '../../mssql/queries/categorypic.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'categorypic');
|
||||
export const { ADD, DELETE_BY_CATEGORY, GET_FIRST_PIC_BY_CATEGORY, GET_NEXT_SORT } = queries;
|
||||
5
src/providers/sqlite3/queries/categorytext.js
Normal file
5
src/providers/sqlite3/queries/categorytext.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as source from '../../mssql/queries/categorytext.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'categorytext');
|
||||
export const { CREATE, UPDATE_BY_CATEGORY_AND_LANGUAGE } = queries;
|
||||
8
src/providers/sqlite3/queries/company.js
Normal file
8
src/providers/sqlite3/queries/company.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import * as source from '../../mssql/queries/company.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'company');
|
||||
export const {
|
||||
CREATE, DELETE, GET_BY_ID, GET_BY_NAME, GET_COMPANY_ROOT_CATEGORY, LIST,
|
||||
UPDATE,
|
||||
} = queries;
|
||||
16
src/providers/sqlite3/queries/companysetting.js
Normal file
16
src/providers/sqlite3/queries/companysetting.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import * as source from '../../mssql/queries/companysetting.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'companysetting');
|
||||
export const {
|
||||
GET_BY_COMPANY_AND_KEY, LIST_BY_COMPANY, LIST_BY_COMPANY_FRONTEND,
|
||||
} = queries;
|
||||
|
||||
export const UPSERT = `
|
||||
INSERT INTO companysetting (company_id, "key", value, frontend, writeonly)
|
||||
VALUES (@companyId, @key, @value, @frontend, @writeonly)
|
||||
ON CONFLICT (company_id, "key") DO UPDATE SET
|
||||
value = excluded.value,
|
||||
frontend = excluded.frontend,
|
||||
writeonly = excluded.writeonly
|
||||
`;
|
||||
5
src/providers/sqlite3/queries/companyuser.js
Normal file
5
src/providers/sqlite3/queries/companyuser.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as source from '../../mssql/queries/companyuser.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'companyuser');
|
||||
export const { ADD, DELETE, GET_BY_EMAIL, GET_COMPANY_ACCESS } = queries;
|
||||
9
src/providers/sqlite3/queries/customergroup.js
Normal file
9
src/providers/sqlite3/queries/customergroup.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as source from '../../mssql/queries/customergroup.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'customergroup');
|
||||
export const {
|
||||
CREATE, CREATE_WITHOUT_COMPANY, DELETE_BY_COMPANY_AND_ID,
|
||||
GET_BY_COMPANY_AND_ID, GET_BY_COMPANY_AND_NAME, GET_NEXT_SORT,
|
||||
LIST_BY_COMPANY, LIST_IDS_BY_COMPANY, SET_COMPANY, UPDATE, UPDATE_SORT,
|
||||
} = queries;
|
||||
22
src/providers/sqlite3/queries/delivery.js
Normal file
22
src/providers/sqlite3/queries/delivery.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import * as source from '../../mssql/queries/delivery.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'delivery');
|
||||
export const {
|
||||
CREATE_ATTACHMENT, CREATE_MANIFEST, DELETE_LINES_BY_MANIFEST,
|
||||
DELETE_MANIFEST, DELIVERY_TYPE, GET_DELIVERY_HEADER, GET_DELIVERY_LINES,
|
||||
GET_NEXT_ATTACHMENT_SORT, LIST_DELIVERIES, LIST_DELIVERY_FILES,
|
||||
UPDATE_MANIFEST,
|
||||
} = queries;
|
||||
|
||||
export const CREATE_FILE = `
|
||||
INSERT INTO "file" (
|
||||
hash, mimetype, name, size, created, modified, content,
|
||||
thumbavif, thumbx, thumby, meta
|
||||
)
|
||||
VALUES (
|
||||
@hash, @mimetype, @name, @size, @created, @modified, @content,
|
||||
@thumbavif, @thumbx, @thumby, @meta
|
||||
)
|
||||
RETURNING id
|
||||
`;
|
||||
26
src/providers/sqlite3/queries/entity.js
Normal file
26
src/providers/sqlite3/queries/entity.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import * as source from '../../mssql/queries/entity.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'entity');
|
||||
export const {
|
||||
CREATE, ENTITY_COLUMNS,
|
||||
GET_BY_COMPANY_AND_ID, GET_CUSTOMER_BY_ID, GET_GROUP_ID_FOR_SUPPLIER,
|
||||
GET_SUPPLIER_BY_ID, LIST_BY_COMPANY, LIST_BY_COMPANY_AND_GROUP,
|
||||
LIST_CUSTOMERS_BY_COMPANY, LIST_SUPPLIERS_BY_COMPANY,
|
||||
NUMBER_TAKEN_IN_COMPANY, POS_GROUP_NAME, SUPPLIER_GROUP_NAME, UPDATE,
|
||||
UPDATE_NAME,
|
||||
} = queries;
|
||||
|
||||
export const DELETE_BY_COMPANY_AND_ID = `
|
||||
DELETE FROM entity
|
||||
WHERE id = @id AND company_id = @companyId
|
||||
`;
|
||||
|
||||
export const DELETE_BY_ID = `
|
||||
DELETE FROM entity
|
||||
WHERE id = @id
|
||||
AND company_id = @companyId
|
||||
AND customergroup_id IN (
|
||||
SELECT id FROM customergroup WHERE company_id = @companyId AND name = @groupName
|
||||
)
|
||||
`;
|
||||
8
src/providers/sqlite3/queries/entityaddress.js
Normal file
8
src/providers/sqlite3/queries/entityaddress.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import * as source from '../../mssql/queries/entityaddress.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'entityaddress');
|
||||
export const {
|
||||
ADDRESS_COLUMNS, CREATE, DELETE_BY_ENTITY_AND_ID, GET_BY_ENTITY_AND_ID,
|
||||
GET_NEXT_SORT, LIST_BY_ENTITY, UPDATE,
|
||||
} = queries;
|
||||
8
src/providers/sqlite3/queries/entitycontact.js
Normal file
8
src/providers/sqlite3/queries/entitycontact.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import * as source from '../../mssql/queries/entitycontact.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'entitycontact');
|
||||
export const {
|
||||
CONTACT_COLUMNS, CREATE, DELETE_BY_ENTITY_AND_ID, GET_BY_ENTITY_AND_ID,
|
||||
GET_NEXT_SORT, LIST_BY_ENTITY, UPDATE,
|
||||
} = queries;
|
||||
5
src/providers/sqlite3/queries/entityhistory.js
Normal file
5
src/providers/sqlite3/queries/entityhistory.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as source from '../../mssql/queries/entityhistory.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'entityhistory');
|
||||
export const { INSERT, LIST_BY_ENTITY_ID } = queries;
|
||||
8
src/providers/sqlite3/queries/google_auth.js
Normal file
8
src/providers/sqlite3/queries/google_auth.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import * as source from '../../mssql/queries/google_auth.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'google_auth');
|
||||
export const {
|
||||
ADD, GET_BY_EMAIL, GET_BY_ID, GOOGLE_AUTH_COLUMNS, LIST_ALLOWED_EMAILS,
|
||||
REMOVE_ALLOWED_EMAIL, SET_PROFILE, SET_SELECTED_COMPANY,
|
||||
} = queries;
|
||||
5
src/providers/sqlite3/queries/language.js
Normal file
5
src/providers/sqlite3/queries/language.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as source from '../../mssql/queries/language.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'language');
|
||||
export const { CREATE, DELETE, GET_BY_ISO639, LIST } = queries;
|
||||
9
src/providers/sqlite3/queries/manifestDoc.js
Normal file
9
src/providers/sqlite3/queries/manifestDoc.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as source from '../../mssql/queries/manifestDoc.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'manifestDoc');
|
||||
export const {
|
||||
CREATE_LINE, CREATE_MANIFEST, DELETE_LINES_BY_MANIFEST, DELETE_MANIFEST,
|
||||
GET_MANIFEST_HEADER, GET_MANIFEST_LINES, LIST_MANIFESTS, ORDER_TYPE,
|
||||
QUOTE_TYPE, UPDATE_MANIFEST,
|
||||
} = queries;
|
||||
21
src/providers/sqlite3/queries/manifestattachment.js
Normal file
21
src/providers/sqlite3/queries/manifestattachment.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as source from '../../mssql/queries/manifestattachment.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'manifestattachment');
|
||||
export const {
|
||||
CHECK_MANIFEST_ACCESS, CREATE_ATTACHMENT, DELETE_ATTACHMENT,
|
||||
DELETE_FILE, FIND_ATTACHMENT, GET_BY_ID, GET_NEXT_SORT, LIST_BY_MANIFEST,
|
||||
LIST_IDS_BY_MANIFEST, SET_TEMP_SORTS, UPDATE_SORT,
|
||||
} = queries;
|
||||
|
||||
export const CREATE_FILE = `
|
||||
INSERT INTO "file" (
|
||||
hash, mimetype, name, size, created, modified, content,
|
||||
thumbavif, thumbx, thumby, meta
|
||||
)
|
||||
VALUES (
|
||||
@hash, @mimetype, @name, @size, @created, @modified, @content,
|
||||
@thumbavif, @thumbx, @thumby, @meta
|
||||
)
|
||||
RETURNING id
|
||||
`;
|
||||
21
src/providers/sqlite3/queries/numbers.js
Normal file
21
src/providers/sqlite3/queries/numbers.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as source from '../../mssql/queries/numbers.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'numbers');
|
||||
export const { GET_BY_COMPANY_AND_ID, LIST_BY_COMPANY, UPDATE_BY_COMPANY_AND_ID } = queries;
|
||||
|
||||
export const ENSURE_TYPE = `
|
||||
INSERT INTO numbers (company_id, "type")
|
||||
VALUES (@companyId, @type)
|
||||
ON CONFLICT (company_id, "type") DO NOTHING
|
||||
`;
|
||||
|
||||
export const GET_FOR_UPDATE = `
|
||||
SELECT id, "type", prefix, padding, "next"
|
||||
FROM numbers
|
||||
WHERE company_id = @companyId AND "type" = @type
|
||||
`;
|
||||
|
||||
export const INCREMENT = `
|
||||
UPDATE numbers SET "next" = "next" + 1 WHERE id = @id
|
||||
`;
|
||||
13
src/providers/sqlite3/queries/pic.js
Normal file
13
src/providers/sqlite3/queries/pic.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as source from '../../mssql/queries/pic.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'pic');
|
||||
export const {
|
||||
CREATE, GET_CATEGORY_ID_BY_HASH, GET_FULL_BY_HASH, GET_THUMB_BY_HASH,
|
||||
} = queries;
|
||||
|
||||
export const DELETE_BY_ARTICLE_AND_HASH = `
|
||||
DELETE FROM pic
|
||||
WHERE hash = @fullHash
|
||||
AND id IN (SELECT pic_id FROM articlepic WHERE article_id = @articleId)
|
||||
`;
|
||||
25
src/providers/sqlite3/queries/posterminal.js
Normal file
25
src/providers/sqlite3/queries/posterminal.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as source from '../../mssql/queries/posterminal.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'posterminal');
|
||||
export const {
|
||||
CREATE, DELETE_BY_COMPANY_AND_ID, GET_BY_ID, LIST_BY_COMPANY,
|
||||
LIST_CATEGORY_IDS, POSTERMINAL_IDS_BY_COMPANY, SET_OFF, SET_OFF_ALL, UPDATE,
|
||||
} = queries;
|
||||
|
||||
export const SET_ON = `
|
||||
INSERT INTO posterminalcategory (posterminal_id, category_id)
|
||||
VALUES (@posterminalId, @categoryId)
|
||||
ON CONFLICT (posterminal_id, category_id) DO NOTHING
|
||||
`;
|
||||
|
||||
export const SET_ON_ALL = `
|
||||
WITH RECURSIVE cats AS (
|
||||
SELECT id FROM category WHERE id = @rootCategoryId
|
||||
UNION ALL
|
||||
SELECT c.id FROM category c INNER JOIN cats ON c.parent_id = cats.id
|
||||
)
|
||||
INSERT INTO posterminalcategory (posterminal_id, category_id)
|
||||
SELECT @posterminalId, id FROM cats WHERE 1
|
||||
ON CONFLICT (posterminal_id, category_id) DO NOTHING
|
||||
`;
|
||||
5
src/providers/sqlite3/queries/price.js
Normal file
5
src/providers/sqlite3/queries/price.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as source from '../../mssql/queries/price.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'price');
|
||||
export const { CREATE, DELETE_BY_ARTICLE_AND_CUSTOMERGROUP } = queries;
|
||||
5
src/providers/sqlite3/queries/tax.js
Normal file
5
src/providers/sqlite3/queries/tax.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as source from '../../mssql/queries/tax.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'tax');
|
||||
export const { CREATE, GET_BY_RATE, LIST } = queries;
|
||||
51
src/providers/sqlite3/queries/translate.js
Normal file
51
src/providers/sqlite3/queries/translate.js
Normal file
@@ -0,0 +1,51 @@
|
||||
function quoteIdentifiers(sql) {
|
||||
return sql.replace(/\[([^\]]+)]/g, '"$1"');
|
||||
}
|
||||
|
||||
function moveReturning(sql) {
|
||||
const output = /\bOUTPUT\s+([\s\S]*?)(?=\bVALUES\b|\bWHERE\b)/i.exec(sql);
|
||||
if (!output) return sql;
|
||||
|
||||
const columns = output[1]
|
||||
.split(',')
|
||||
.map((column) => column.trim().replace(/\bINSERTED\./gi, ''))
|
||||
.join(', ');
|
||||
const withoutOutput = sql.slice(0, output.index) + sql.slice(output.index + output[0].length);
|
||||
return `${withoutOutput.trim().replace(/;$/, '')}\nRETURNING ${columns}`;
|
||||
}
|
||||
|
||||
function moveTop(sql) {
|
||||
const matches = [...sql.matchAll(/\bTOP\s*\(?\s*(\d+)\s*\)?\s*/gi)];
|
||||
if (matches.length === 0) return sql;
|
||||
if (matches.length > 1) {
|
||||
// Complex nested queries are supplied as compatibility exports until
|
||||
// their provider-specific store paths are converted. Do not corrupt them
|
||||
// by appending one LIMIT to the outer statement.
|
||||
return sql;
|
||||
}
|
||||
return `${sql.replace(matches[0][0], '').trim().replace(/;$/, '')}\nLIMIT ${matches[0][1]}`;
|
||||
}
|
||||
|
||||
/** Convert the common, context-free subset of MSSQL query syntax to SQLite. */
|
||||
export function sqliteQuery(sql) {
|
||||
if (typeof sql !== 'string') return sql;
|
||||
|
||||
let result = quoteIdentifiers(sql)
|
||||
.replace(/\bISNULL\s*\(/gi, 'IFNULL(')
|
||||
.replace(/\bSYSUTCDATETIME\s*\(\s*\)/gi, "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')")
|
||||
.replace(/\bCAST\s*\(([^()]+?)\s+AS\s+INT\s*\)/gi, 'CAST($1 AS INTEGER)');
|
||||
result = moveReturning(result);
|
||||
result = moveTop(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function translateQueries(module, moduleName) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(module).map(([name, value]) => [
|
||||
name,
|
||||
typeof value === 'string' && /\s/.test(value)
|
||||
? sqliteQuery(value)
|
||||
: value,
|
||||
]),
|
||||
);
|
||||
}
|
||||
8
src/providers/sqlite3/queries/unit.js
Normal file
8
src/providers/sqlite3/queries/unit.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import * as source from '../../mssql/queries/unit.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'unit');
|
||||
export const {
|
||||
CREATE, DELETE_BY_COMPANY_AND_SI, GET_BY_SI, GET_FULL_BY_SI,
|
||||
LIST_BY_COMPANY, UNIT_COLUMNS, UPDATE_BY_SI,
|
||||
} = queries;
|
||||
5
src/providers/sqlite3/queries/unittext.js
Normal file
5
src/providers/sqlite3/queries/unittext.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as source from '../../mssql/queries/unittext.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'unittext');
|
||||
export const { CREATE, GET_BY_UNIT_AND_LANGUAGE, UPDATE_BY_UNIT_AND_LANGUAGE } = queries;
|
||||
622
src/providers/sqlite3/schema.js
Normal file
622
src/providers/sqlite3/schema.js
Normal file
@@ -0,0 +1,622 @@
|
||||
const UTC_NOW = "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
|
||||
|
||||
export const INIT_META = `
|
||||
CREATE TABLE IF NOT EXISTS meta (
|
||||
"key" TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
INSERT OR IGNORE INTO meta ("key", value) VALUES ('version', '1');
|
||||
`;
|
||||
|
||||
export const INIT_LANGUAGE = `
|
||||
CREATE TABLE IF NOT EXISTS language (
|
||||
id INTEGER PRIMARY KEY,
|
||||
iso639 TEXT NOT NULL COLLATE NOCASE UNIQUE
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_TAX = `
|
||||
CREATE TABLE IF NOT EXISTS tax (
|
||||
id INTEGER PRIMARY KEY,
|
||||
rate NUMERIC NOT NULL UNIQUE
|
||||
);
|
||||
INSERT OR IGNORE INTO tax (rate) VALUES (0), (7), (19);
|
||||
`;
|
||||
|
||||
export const INIT_CATEGORY = `
|
||||
CREATE TABLE IF NOT EXISTS category (
|
||||
id INTEGER PRIMARY KEY,
|
||||
parent_id INTEGER,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY (parent_id) REFERENCES category(id) ON DELETE NO ACTION
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_PIC = `
|
||||
CREATE TABLE IF NOT EXISTS pic (
|
||||
id INTEGER PRIMARY KEY,
|
||||
w INTEGER NOT NULL,
|
||||
h INTEGER NOT NULL,
|
||||
size INTEGER NOT NULL,
|
||||
hash TEXT NOT NULL UNIQUE,
|
||||
thumbw INTEGER NOT NULL,
|
||||
thumbh INTEGER NOT NULL,
|
||||
thumbhash TEXT NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
fullbinary BLOB NOT NULL,
|
||||
thumbbinary BLOB NOT NULL
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_COMPANY = `
|
||||
CREATE TABLE IF NOT EXISTS company (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL COLLATE NOCASE UNIQUE,
|
||||
root_category_id INTEGER,
|
||||
default_language_id INTEGER,
|
||||
taxrate INTEGER,
|
||||
reducedtaxrate INTEGER,
|
||||
taxfreerate INTEGER,
|
||||
poscustomergroup_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (root_category_id) REFERENCES category(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (default_language_id) REFERENCES language(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (taxrate) REFERENCES tax(id) ON DELETE NO ACTION,
|
||||
FOREIGN KEY (reducedtaxrate) REFERENCES tax(id) ON DELETE NO ACTION,
|
||||
FOREIGN KEY (taxfreerate) REFERENCES tax(id) ON DELETE NO ACTION,
|
||||
FOREIGN KEY (poscustomergroup_id) REFERENCES customergroup(id) ON DELETE NO ACTION
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_GOOGLE_AUTH = `
|
||||
CREATE TABLE IF NOT EXISTS google_auth (
|
||||
id INTEGER PRIMARY KEY,
|
||||
email TEXT NOT NULL COLLATE NOCASE UNIQUE,
|
||||
picture TEXT,
|
||||
picturebinary BLOB,
|
||||
sub TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
selected_company INTEGER,
|
||||
FOREIGN KEY (selected_company) REFERENCES company(id) ON DELETE SET NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS UQ_google_auth_sub
|
||||
ON google_auth(sub) WHERE sub IS NOT NULL;
|
||||
`;
|
||||
|
||||
export const INIT_CUSTOMERGROUP = `
|
||||
CREATE TABLE IF NOT EXISTS customergroup (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
company_id INTEGER,
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
/* The FK is declared inline in INIT_COMPANY; SQLite cannot add it later. */
|
||||
export const INIT_COMPANY_POS_CUSTOMERGROUP_FK = '';
|
||||
|
||||
export const INIT_ENTITY = `
|
||||
CREATE TABLE IF NOT EXISTS entity (
|
||||
id INTEGER PRIMARY KEY,
|
||||
customergroup_id INTEGER NOT NULL,
|
||||
company_id INTEGER,
|
||||
first_name TEXT,
|
||||
name TEXT NOT NULL,
|
||||
"number" TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
updated_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
FOREIGN KEY (customergroup_id) REFERENCES customergroup(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
/**
|
||||
* SQLite cannot index a joined view, so company_id is denormalized onto entity.
|
||||
* Triggers keep it synchronized with the entity's customer group.
|
||||
*/
|
||||
export const INIT_ENTITY_COMPANY_NUMBER_UQ = `
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS UQ_entity_company_number
|
||||
ON entity(company_id, "number") WHERE "number" IS NOT NULL;
|
||||
CREATE TRIGGER IF NOT EXISTS TR_entity_set_company_insert
|
||||
AFTER INSERT ON entity
|
||||
BEGIN
|
||||
UPDATE entity
|
||||
SET company_id = (SELECT company_id FROM customergroup WHERE id = NEW.customergroup_id)
|
||||
WHERE id = NEW.id;
|
||||
END;
|
||||
CREATE TRIGGER IF NOT EXISTS TR_entity_set_company_update
|
||||
AFTER UPDATE OF customergroup_id ON entity
|
||||
BEGIN
|
||||
UPDATE entity
|
||||
SET company_id = (SELECT company_id FROM customergroup WHERE id = NEW.customergroup_id)
|
||||
WHERE id = NEW.id;
|
||||
END;
|
||||
CREATE TRIGGER IF NOT EXISTS TR_customergroup_sync_entity_company
|
||||
AFTER UPDATE OF company_id ON customergroup
|
||||
BEGIN
|
||||
UPDATE entity SET company_id = NEW.company_id WHERE customergroup_id = NEW.id;
|
||||
END;
|
||||
`;
|
||||
|
||||
export const INIT_ENTITYADDRESS = `
|
||||
CREATE TABLE IF NOT EXISTS entityaddress (
|
||||
id INTEGER PRIMARY KEY,
|
||||
entity_id INTEGER NOT NULL,
|
||||
first_name TEXT,
|
||||
name TEXT,
|
||||
street TEXT,
|
||||
streetnumber TEXT,
|
||||
zip TEXT,
|
||||
city TEXT,
|
||||
county TEXT,
|
||||
addresstype TEXT NOT NULL,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_ENTITYCONTACT = `
|
||||
CREATE TABLE IF NOT EXISTS entitycontact (
|
||||
id INTEGER PRIMARY KEY,
|
||||
entity_id INTEGER NOT NULL,
|
||||
contacttype TEXT NOT NULL,
|
||||
contact TEXT NOT NULL,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_UNIT = `
|
||||
CREATE TABLE IF NOT EXISTS unit (
|
||||
id INTEGER PRIMARY KEY,
|
||||
company_id INTEGER NOT NULL,
|
||||
si TEXT NOT NULL,
|
||||
basesi TEXT NOT NULL,
|
||||
basesifactor REAL NOT NULL,
|
||||
UNIQUE (company_id, si),
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_UNITTEXT = `
|
||||
CREATE TABLE IF NOT EXISTS unittext (
|
||||
id INTEGER PRIMARY KEY,
|
||||
unit_id INTEGER NOT NULL,
|
||||
language_id INTEGER NOT NULL,
|
||||
symbol TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
UNIQUE (unit_id, language_id),
|
||||
FOREIGN KEY (unit_id) REFERENCES unit(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (language_id) REFERENCES language(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_OPTIONSLIST = `
|
||||
CREATE TABLE IF NOT EXISTS optionslist (id INTEGER PRIMARY KEY);
|
||||
`;
|
||||
|
||||
export const INIT_ARTICLELIST = `
|
||||
CREATE TABLE IF NOT EXISTS articlelist (id INTEGER PRIMARY KEY);
|
||||
`;
|
||||
|
||||
export const INIT_BRAND = `
|
||||
CREATE TABLE IF NOT EXISTS brand (
|
||||
id INTEGER PRIMARY KEY,
|
||||
company_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL COLLATE NOCASE,
|
||||
gsprpinfo TEXT,
|
||||
UNIQUE (company_id, name),
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_ARTICLE = `
|
||||
CREATE TABLE IF NOT EXISTS article (
|
||||
id INTEGER PRIMARY KEY,
|
||||
sku TEXT COLLATE NOCASE UNIQUE,
|
||||
unit_id INTEGER NOT NULL,
|
||||
unitamount REAL NOT NULL,
|
||||
weight REAL NOT NULL,
|
||||
brand_id INTEGER,
|
||||
articlelist_id INTEGER,
|
||||
optionslist_id INTEGER,
|
||||
is_father INTEGER NOT NULL DEFAULT 0,
|
||||
fatherarticle_id INTEGER,
|
||||
fathersort INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
updated_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
FOREIGN KEY (unit_id) REFERENCES unit(id) ON DELETE NO ACTION,
|
||||
FOREIGN KEY (brand_id) REFERENCES brand(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (articlelist_id) REFERENCES articlelist(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (optionslist_id) REFERENCES optionslist(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (fatherarticle_id) REFERENCES article(id) ON DELETE NO ACTION
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS UQ_article_fatherarticle_fathersort
|
||||
ON article(fatherarticle_id, fathersort)
|
||||
WHERE fatherarticle_id IS NOT NULL;
|
||||
`;
|
||||
|
||||
export const INIT_ARTICLEEAN = `
|
||||
CREATE TABLE IF NOT EXISTS articleean (
|
||||
id INTEGER PRIMARY KEY,
|
||||
article_id INTEGER NOT NULL,
|
||||
ean TEXT NOT NULL UNIQUE,
|
||||
FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS IX_articleean_article_ean
|
||||
ON articleean(article_id, ean);
|
||||
`;
|
||||
|
||||
export const INIT_ARTICLEPIC = `
|
||||
CREATE TABLE IF NOT EXISTS articlepic (
|
||||
id INTEGER PRIMARY KEY,
|
||||
article_id INTEGER NOT NULL,
|
||||
pic_id INTEGER NOT NULL,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE (article_id, pic_id),
|
||||
UNIQUE (article_id, sort),
|
||||
FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (pic_id) REFERENCES pic(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_BRANDPIC = `
|
||||
CREATE TABLE IF NOT EXISTS brandpic (
|
||||
id INTEGER PRIMARY KEY,
|
||||
brand_id INTEGER NOT NULL,
|
||||
pic_id INTEGER NOT NULL,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE (brand_id, pic_id),
|
||||
UNIQUE (brand_id, sort),
|
||||
FOREIGN KEY (brand_id) REFERENCES brand(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (pic_id) REFERENCES pic(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_ARTICLELISTARTICLE = `
|
||||
CREATE TABLE IF NOT EXISTS articlelistarticle (
|
||||
id INTEGER PRIMARY KEY,
|
||||
articlelist_id INTEGER NOT NULL,
|
||||
article_id INTEGER NOT NULL,
|
||||
count INTEGER NOT NULL DEFAULT 1,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE (articlelist_id, article_id),
|
||||
UNIQUE (articlelist_id, sort),
|
||||
FOREIGN KEY (articlelist_id) REFERENCES articlelist(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_OPTIONSLISTARTICLE = `
|
||||
CREATE TABLE IF NOT EXISTS optionslistarticle (
|
||||
id INTEGER PRIMARY KEY,
|
||||
optionslist_id INTEGER NOT NULL,
|
||||
article_id INTEGER NOT NULL,
|
||||
count INTEGER NOT NULL DEFAULT 1,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
mandated INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE (optionslist_id, article_id),
|
||||
UNIQUE (optionslist_id, sort),
|
||||
FOREIGN KEY (optionslist_id) REFERENCES optionslist(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_CATEGORYARTICLE = `
|
||||
CREATE TABLE IF NOT EXISTS categoryarticle (
|
||||
id INTEGER PRIMARY KEY,
|
||||
category_id INTEGER NOT NULL,
|
||||
article_id INTEGER NOT NULL,
|
||||
UNIQUE (category_id, article_id),
|
||||
FOREIGN KEY (category_id) REFERENCES category(id) ON DELETE NO ACTION,
|
||||
FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS IX_categoryarticle_article_category
|
||||
ON categoryarticle(article_id, category_id);
|
||||
`;
|
||||
|
||||
export const INIT_CATEGORYPIC = `
|
||||
CREATE TABLE IF NOT EXISTS categorypic (
|
||||
id INTEGER PRIMARY KEY,
|
||||
category_id INTEGER NOT NULL,
|
||||
pic_id INTEGER NOT NULL,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE (category_id, pic_id),
|
||||
UNIQUE (category_id, sort),
|
||||
FOREIGN KEY (category_id) REFERENCES category(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (pic_id) REFERENCES pic(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_CATEGORYTEXT = `
|
||||
CREATE TABLE IF NOT EXISTS categorytext (
|
||||
id INTEGER PRIMARY KEY,
|
||||
category_id INTEGER NOT NULL,
|
||||
language_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
slug TEXT NOT NULL,
|
||||
seo TEXT,
|
||||
"desc" TEXT,
|
||||
UNIQUE (category_id, language_id),
|
||||
FOREIGN KEY (category_id) REFERENCES category(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (language_id) REFERENCES language(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_ARTICLENAME = `
|
||||
CREATE TABLE IF NOT EXISTS articletext (
|
||||
id INTEGER PRIMARY KEY,
|
||||
article_id INTEGER NOT NULL,
|
||||
language_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
slug TEXT NOT NULL,
|
||||
seo TEXT,
|
||||
"desc" TEXT,
|
||||
UNIQUE (article_id, language_id),
|
||||
FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (language_id) REFERENCES language(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_PRICE = `
|
||||
CREATE TABLE IF NOT EXISTS price (
|
||||
id INTEGER PRIMARY KEY,
|
||||
tax_id INTEGER NOT NULL,
|
||||
article_id INTEGER NOT NULL,
|
||||
minmultiplier NUMERIC NOT NULL DEFAULT 1,
|
||||
customergroup_id INTEGER NOT NULL,
|
||||
net REAL NOT NULL,
|
||||
FOREIGN KEY (tax_id) REFERENCES tax(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (customergroup_id) REFERENCES customergroup(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS IX_price_article_cg_mult
|
||||
ON price(article_id, customergroup_id, minmultiplier, net, tax_id);
|
||||
`;
|
||||
|
||||
export const INIT_ARTICLE_SUPPLIER = `
|
||||
CREATE TABLE IF NOT EXISTS articlesupplier (
|
||||
id INTEGER PRIMARY KEY,
|
||||
article_id INTEGER NOT NULL,
|
||||
entity_id INTEGER NOT NULL,
|
||||
price_id INTEGER,
|
||||
sku TEXT,
|
||||
stock REAL,
|
||||
pu REAL NOT NULL DEFAULT 1,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE (article_id, entity_id),
|
||||
UNIQUE (article_id, sort),
|
||||
FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (price_id) REFERENCES price(id) ON DELETE NO ACTION
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS IX_articlesupplier_article_entity
|
||||
ON articlesupplier(article_id, entity_id);
|
||||
`;
|
||||
|
||||
export const INIT_MANIFEST = `
|
||||
CREATE TABLE IF NOT EXISTS manifest (
|
||||
id INTEGER PRIMARY KEY,
|
||||
company_id INTEGER NOT NULL,
|
||||
"number" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
entity_id INTEGER,
|
||||
external_number TEXT,
|
||||
"date" TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE NO ACTION,
|
||||
FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE SET NULL
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_MANIFESTARTICLE = `
|
||||
CREATE TABLE IF NOT EXISTS manifestarticle (
|
||||
id INTEGER PRIMARY KEY,
|
||||
manifest_id INTEGER NOT NULL,
|
||||
sku TEXT,
|
||||
name TEXT,
|
||||
pu REAL NOT NULL DEFAULT 1,
|
||||
amount REAL NOT NULL,
|
||||
net REAL NOT NULL,
|
||||
comment TEXT,
|
||||
rebate REAL NOT NULL DEFAULT 0,
|
||||
taxrate REAL NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY (manifest_id) REFERENCES manifest(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
/* SQLite has no SQL Server ledger tables; constraints preserve the base shape. */
|
||||
export const INIT_LEDGER = `
|
||||
CREATE TABLE IF NOT EXISTS ledger (
|
||||
id INTEGER PRIMARY KEY,
|
||||
manifestarticle_id INTEGER NOT NULL,
|
||||
transaction_type TEXT NOT NULL,
|
||||
amount REAL NOT NULL,
|
||||
transaction_text TEXT NOT NULL,
|
||||
hash_version INTEGER NOT NULL,
|
||||
prev_hash TEXT,
|
||||
FOREIGN KEY (manifestarticle_id) REFERENCES manifestarticle(id) ON DELETE NO ACTION
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_NUMBERS = `
|
||||
CREATE TABLE IF NOT EXISTS numbers (
|
||||
id INTEGER PRIMARY KEY,
|
||||
company_id INTEGER NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
prefix TEXT NOT NULL DEFAULT '',
|
||||
padding INTEGER NOT NULL DEFAULT 0,
|
||||
"next" INTEGER NOT NULL DEFAULT 1,
|
||||
UNIQUE (company_id, "type"),
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_COMPANYUSER = `
|
||||
CREATE TABLE IF NOT EXISTS companyuser (
|
||||
id INTEGER PRIMARY KEY,
|
||||
google_auth_id INTEGER NOT NULL,
|
||||
company_id INTEGER NOT NULL,
|
||||
UNIQUE (google_auth_id, company_id),
|
||||
FOREIGN KEY (google_auth_id) REFERENCES google_auth(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_ARTICLEHISTORY = `
|
||||
CREATE TABLE IF NOT EXISTS articlehistory (
|
||||
id INTEGER PRIMARY KEY,
|
||||
article_id INTEGER NOT NULL,
|
||||
companyuser_id INTEGER,
|
||||
"date" TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
"text" TEXT NOT NULL,
|
||||
FOREIGN KEY (article_id) REFERENCES article(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (companyuser_id) REFERENCES companyuser(id) ON DELETE SET NULL
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_ENTITYHISTORY = `
|
||||
CREATE TABLE IF NOT EXISTS entityhistory (
|
||||
id INTEGER PRIMARY KEY,
|
||||
entity_id INTEGER NOT NULL,
|
||||
companyuser_id INTEGER,
|
||||
"date" TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
"text" TEXT NOT NULL,
|
||||
FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (companyuser_id) REFERENCES companyuser(id) ON DELETE NO ACTION
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_SYNCPOS = `
|
||||
CREATE TABLE IF NOT EXISTS syncpos (
|
||||
id INTEGER PRIMARY KEY,
|
||||
company_id INTEGER NOT NULL,
|
||||
paircode TEXT NOT NULL UNIQUE,
|
||||
token TEXT UNIQUE,
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_COMPANYSETTING = `
|
||||
CREATE TABLE IF NOT EXISTS companysetting (
|
||||
id INTEGER PRIMARY KEY,
|
||||
company_id INTEGER NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
frontend INTEGER NOT NULL DEFAULT 0,
|
||||
writeonly INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE (company_id, "key"),
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_POSTERMINAL = `
|
||||
CREATE TABLE IF NOT EXISTS posterminal (
|
||||
id INTEGER PRIMARY KEY,
|
||||
company_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
token TEXT,
|
||||
pairingpin TEXT NOT NULL,
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS UQ_posterminal_token
|
||||
ON posterminal(token) WHERE token IS NOT NULL;
|
||||
`;
|
||||
|
||||
export const INIT_POSTERMINALCATEGORY = `
|
||||
CREATE TABLE IF NOT EXISTS posterminalcategory (
|
||||
id INTEGER PRIMARY KEY,
|
||||
posterminal_id INTEGER NOT NULL,
|
||||
category_id INTEGER NOT NULL,
|
||||
UNIQUE (posterminal_id, category_id),
|
||||
FOREIGN KEY (posterminal_id) REFERENCES posterminal(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (category_id) REFERENCES category(id) ON DELETE NO ACTION
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_FILE = `
|
||||
CREATE TABLE IF NOT EXISTS "file" (
|
||||
id INTEGER PRIMARY KEY,
|
||||
hash TEXT NOT NULL,
|
||||
mimetype TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
size INTEGER NOT NULL,
|
||||
created TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
modified TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
content BLOB NOT NULL,
|
||||
thumbavif BLOB,
|
||||
thumbx INTEGER,
|
||||
thumby INTEGER,
|
||||
meta TEXT
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_MANIFESTATTACHMENT = `
|
||||
CREATE TABLE IF NOT EXISTS manifestattachment (
|
||||
id INTEGER PRIMARY KEY,
|
||||
manifest_id INTEGER NOT NULL,
|
||||
file_id INTEGER NOT NULL,
|
||||
sort INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE (manifest_id, sort),
|
||||
FOREIGN KEY (manifest_id) REFERENCES manifest(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (file_id) REFERENCES "file"(id) ON DELETE CASCADE
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_MANIFESTATTACHMENT_TRIGGER = `
|
||||
CREATE TRIGGER IF NOT EXISTS TR_manifestattachment_delete_file
|
||||
AFTER DELETE ON manifestattachment
|
||||
BEGIN
|
||||
DELETE FROM "file"
|
||||
WHERE id = OLD.file_id
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM manifestattachment WHERE file_id = OLD.file_id
|
||||
);
|
||||
END;
|
||||
`;
|
||||
|
||||
/** DDL scripts in dependency order for SQLite schema initialization. */
|
||||
export const SCHEMA_SCRIPTS = [
|
||||
INIT_META,
|
||||
INIT_LANGUAGE,
|
||||
INIT_TAX,
|
||||
INIT_CATEGORY,
|
||||
INIT_PIC,
|
||||
INIT_COMPANY,
|
||||
INIT_GOOGLE_AUTH,
|
||||
INIT_CUSTOMERGROUP,
|
||||
INIT_ENTITY,
|
||||
INIT_ENTITY_COMPANY_NUMBER_UQ,
|
||||
INIT_ENTITYADDRESS,
|
||||
INIT_ENTITYCONTACT,
|
||||
INIT_UNIT,
|
||||
INIT_UNITTEXT,
|
||||
INIT_OPTIONSLIST,
|
||||
INIT_ARTICLELIST,
|
||||
INIT_BRAND,
|
||||
INIT_ARTICLE,
|
||||
INIT_ARTICLEEAN,
|
||||
INIT_ARTICLEPIC,
|
||||
INIT_BRANDPIC,
|
||||
INIT_ARTICLELISTARTICLE,
|
||||
INIT_OPTIONSLISTARTICLE,
|
||||
INIT_CATEGORYARTICLE,
|
||||
INIT_CATEGORYPIC,
|
||||
INIT_CATEGORYTEXT,
|
||||
INIT_ARTICLENAME,
|
||||
INIT_PRICE,
|
||||
INIT_ARTICLE_SUPPLIER,
|
||||
INIT_MANIFEST,
|
||||
INIT_MANIFESTARTICLE,
|
||||
INIT_LEDGER,
|
||||
INIT_NUMBERS,
|
||||
INIT_COMPANYUSER,
|
||||
INIT_ARTICLEHISTORY,
|
||||
INIT_ENTITYHISTORY,
|
||||
INIT_SYNCPOS,
|
||||
INIT_COMPANYSETTING,
|
||||
INIT_POSTERMINAL,
|
||||
INIT_POSTERMINALCATEGORY,
|
||||
INIT_FILE,
|
||||
INIT_MANIFESTATTACHMENT,
|
||||
INIT_MANIFESTATTACHMENT_TRIGGER,
|
||||
];
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ADD } from '../../providers/mssql/queries/google_auth.js';
|
||||
import { ADD as MSSQL_ADD } from '../../providers/mssql/queries/google_auth.js';
|
||||
import { ADD as SQLITE_ADD } from '../../providers/sqlite3/queries/google_auth.js';
|
||||
|
||||
export default function add(pool) {
|
||||
export default function add(provider) {
|
||||
const sql = provider.dialect === 'sqlite3' ? SQLITE_ADD : MSSQL_ADD;
|
||||
/**
|
||||
* Adds a new allowed Google auth email.
|
||||
*
|
||||
@@ -12,11 +14,7 @@ export default function add(pool) {
|
||||
* // => 1
|
||||
*/
|
||||
return async (email) => {
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('email', email)
|
||||
.query(ADD);
|
||||
|
||||
return result.recordset[0].id;
|
||||
const result = await provider.query(sql, { email });
|
||||
return result.rows[0]?.id ?? result.insertId;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { GET_BY_EMAIL } from '../../providers/mssql/queries/google_auth.js';
|
||||
import { GET_BY_EMAIL as MSSQL_GET } from '../../providers/mssql/queries/google_auth.js';
|
||||
import { GET_BY_EMAIL as SQLITE_GET } from '../../providers/sqlite3/queries/google_auth.js';
|
||||
|
||||
export default function getByEmail(pool) {
|
||||
export default function getByEmail(provider) {
|
||||
const sql = provider.dialect === 'sqlite3' ? SQLITE_GET : MSSQL_GET;
|
||||
/**
|
||||
* Returns a Google auth row by email.
|
||||
*
|
||||
@@ -12,11 +14,7 @@ export default function getByEmail(pool) {
|
||||
* // => { id, email, ... }
|
||||
*/
|
||||
return async (email) => {
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('email', email)
|
||||
.query(GET_BY_EMAIL);
|
||||
|
||||
return result.recordset[0];
|
||||
const result = await provider.query(sql, { email });
|
||||
return result.rows[0];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { GET_BY_ID } from '../../providers/mssql/queries/google_auth.js';
|
||||
import { GET_BY_ID as MSSQL_GET } from '../../providers/mssql/queries/google_auth.js';
|
||||
import { GET_BY_ID as SQLITE_GET } from '../../providers/sqlite3/queries/google_auth.js';
|
||||
|
||||
export default function getById(pool) {
|
||||
export default function getById(provider) {
|
||||
const sql = provider.dialect === 'sqlite3' ? SQLITE_GET : MSSQL_GET;
|
||||
/**
|
||||
* Returns a Google auth row by its id.
|
||||
*
|
||||
@@ -12,11 +14,7 @@ export default function getById(pool) {
|
||||
* // => { id, email, ... }
|
||||
*/
|
||||
return async (googleAuthId) => {
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('googleAuthId', googleAuthId)
|
||||
.query(GET_BY_ID);
|
||||
|
||||
return result.recordset[0];
|
||||
const result = await provider.query(sql, { googleAuthId });
|
||||
return result.rows[0];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { LIST_ALLOWED_EMAILS } from '../../providers/mssql/queries/google_auth.js';
|
||||
import { LIST_ALLOWED_EMAILS as MSSQL_LIST } from '../../providers/mssql/queries/google_auth.js';
|
||||
import { LIST_ALLOWED_EMAILS as SQLITE_LIST } from '../../providers/sqlite3/queries/google_auth.js';
|
||||
|
||||
export default function listAllowedEmails(pool) {
|
||||
export default function listAllowedEmails(provider) {
|
||||
const sql = provider.dialect === 'sqlite3' ? SQLITE_LIST : MSSQL_LIST;
|
||||
/**
|
||||
* Lists all allowed Google auth emails.
|
||||
*
|
||||
@@ -11,7 +13,7 @@ export default function listAllowedEmails(pool) {
|
||||
* // => [{ id, email }]
|
||||
*/
|
||||
return async () => {
|
||||
const result = await pool.request().query(LIST_ALLOWED_EMAILS);
|
||||
return result.recordset;
|
||||
const result = await provider.query(sql);
|
||||
return result.rows;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { REMOVE_ALLOWED_EMAIL } from '../../providers/mssql/queries/google_auth.js';
|
||||
import { REMOVE_ALLOWED_EMAIL as MSSQL_REMOVE } from '../../providers/mssql/queries/google_auth.js';
|
||||
import { REMOVE_ALLOWED_EMAIL as SQLITE_REMOVE } from '../../providers/sqlite3/queries/google_auth.js';
|
||||
|
||||
export default function removeAllowedEmail(pool) {
|
||||
export default function removeAllowedEmail(provider) {
|
||||
const sql = provider.dialect === 'sqlite3' ? SQLITE_REMOVE : MSSQL_REMOVE;
|
||||
/**
|
||||
* Removes an allowed Google auth email.
|
||||
*
|
||||
@@ -11,9 +13,6 @@ export default function removeAllowedEmail(pool) {
|
||||
* await store.google_auth.removeAllowedEmail('user@example.com');
|
||||
*/
|
||||
return async (email) => {
|
||||
await pool
|
||||
.request()
|
||||
.input('email', email)
|
||||
.query(REMOVE_ALLOWED_EMAIL);
|
||||
await provider.query(sql, { email });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { SET_PROFILE } from '../../providers/mssql/queries/google_auth.js';
|
||||
import { SET_PROFILE as MSSQL_SET } from '../../providers/mssql/queries/google_auth.js';
|
||||
import { SET_PROFILE as SQLITE_SET } from '../../providers/sqlite3/queries/google_auth.js';
|
||||
|
||||
export default function setProfile(pool) {
|
||||
export default function setProfile(provider) {
|
||||
const sql = provider.dialect === 'sqlite3' ? SQLITE_SET : MSSQL_SET;
|
||||
/**
|
||||
* Updates the profile (picture, picture binary, sub) of a Google auth user.
|
||||
*
|
||||
@@ -23,15 +25,14 @@ export default function setProfile(pool) {
|
||||
throw new Error('Invalid pictureBinary');
|
||||
}
|
||||
|
||||
const result = await pool
|
||||
.request()
|
||||
.input('googleAuthId', googleAuthId)
|
||||
.input('picture', picture)
|
||||
.input('pictureBinary', pictureBinary)
|
||||
.input('sub', sub)
|
||||
.query(SET_PROFILE);
|
||||
const result = await provider.query(sql, {
|
||||
googleAuthId,
|
||||
picture,
|
||||
pictureBinary,
|
||||
sub,
|
||||
});
|
||||
|
||||
if (!result.rowsAffected[0]) {
|
||||
if (!result.rowsAffected) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { SET_SELECTED_COMPANY } from '../../providers/mssql/queries/google_auth.js';
|
||||
import { SET_SELECTED_COMPANY as MSSQL_SET } from '../../providers/mssql/queries/google_auth.js';
|
||||
import { SET_SELECTED_COMPANY as SQLITE_SET } from '../../providers/sqlite3/queries/google_auth.js';
|
||||
|
||||
export default function setSelectedCompany(pool) {
|
||||
export default function setSelectedCompany(provider) {
|
||||
const sql = provider.dialect === 'sqlite3' ? SQLITE_SET : MSSQL_SET;
|
||||
/**
|
||||
* Sets the currently selected company for a Google auth user.
|
||||
*
|
||||
@@ -12,10 +14,6 @@ export default function setSelectedCompany(pool) {
|
||||
* await store.google_auth.setSelectedCompany(42, 1);
|
||||
*/
|
||||
return async (googleAuthId, companyId) => {
|
||||
await pool
|
||||
.request()
|
||||
.input('googleAuthId', googleAuthId)
|
||||
.input('companyId', companyId)
|
||||
.query(SET_SELECTED_COMPANY);
|
||||
await provider.query(sql, { googleAuthId, companyId });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { CREATE } from '../../providers/mssql/queries/language.js';
|
||||
import request from '../request.js';
|
||||
import { CREATE as MSSQL_CREATE } from '../../providers/mssql/queries/language.js';
|
||||
import { CREATE as SQLITE_CREATE } from '../../providers/sqlite3/queries/language.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function create(pool) {
|
||||
export default function create(provider) {
|
||||
const sql = provider.dialect === 'sqlite3' ? SQLITE_CREATE : MSSQL_CREATE;
|
||||
/**
|
||||
* Creates a new language by its ISO 639 code.
|
||||
*
|
||||
@@ -14,10 +16,7 @@ export default function create(pool) {
|
||||
* // => 1
|
||||
*/
|
||||
return async (iso639, tx) => {
|
||||
const result = await request(pool, tx)
|
||||
.input('iso639', iso639)
|
||||
.query(CREATE);
|
||||
|
||||
return result.recordset[0].id;
|
||||
const result = await makeRunner(provider, tx).query(sql, { iso639 });
|
||||
return result.rows[0]?.id ?? result.insertId;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { DELETE } from '../../providers/mssql/queries/language.js';
|
||||
import { DELETE as MSSQL_DELETE } from '../../providers/mssql/queries/language.js';
|
||||
import { DELETE as SQLITE_DELETE } from '../../providers/sqlite3/queries/language.js';
|
||||
|
||||
export default function del(pool) {
|
||||
export default function del(provider) {
|
||||
const sql = provider.dialect === 'sqlite3' ? SQLITE_DELETE : MSSQL_DELETE;
|
||||
/**
|
||||
* Deletes a language by its id.
|
||||
*
|
||||
@@ -11,9 +13,6 @@ export default function del(pool) {
|
||||
* await store.lang.delete(1);
|
||||
*/
|
||||
return async (id) => {
|
||||
await pool
|
||||
.request()
|
||||
.input('id', id)
|
||||
.query(DELETE);
|
||||
await provider.query(sql, { id });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import getByIso639 from './getByIso639.js';
|
||||
import create from './create.js';
|
||||
|
||||
export default function ensure(pool) {
|
||||
const get = getByIso639(pool);
|
||||
const add = create(pool);
|
||||
export default function ensure(provider) {
|
||||
const get = getByIso639(provider);
|
||||
const add = create(provider);
|
||||
/**
|
||||
* Returns the id of a language by ISO code, creating it if it does not exist.
|
||||
*
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { GET_BY_ISO639 } from '../../providers/mssql/queries/language.js';
|
||||
import request from '../request.js';
|
||||
import { GET_BY_ISO639 as MSSQL_GET } from '../../providers/mssql/queries/language.js';
|
||||
import { GET_BY_ISO639 as SQLITE_GET } from '../../providers/sqlite3/queries/language.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function getByIso639(pool) {
|
||||
export default function getByIso639(provider) {
|
||||
const sql = provider.dialect === 'sqlite3' ? SQLITE_GET : MSSQL_GET;
|
||||
/**
|
||||
* Returns a language row by its ISO 639 code.
|
||||
*
|
||||
@@ -14,10 +16,7 @@ export default function getByIso639(pool) {
|
||||
* // => { id: 1, iso639: 'de' }
|
||||
*/
|
||||
return async (iso639, tx) => {
|
||||
const result = await request(pool, tx)
|
||||
.input('iso639', iso639)
|
||||
.query(GET_BY_ISO639);
|
||||
|
||||
return result.recordset[0];
|
||||
const result = await makeRunner(provider, tx).query(sql, { iso639 });
|
||||
return result.rows[0];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { LIST } from '../../providers/mssql/queries/language.js';
|
||||
import { LIST as MSSQL_LIST } from '../../providers/mssql/queries/language.js';
|
||||
import { LIST as SQLITE_LIST } from '../../providers/sqlite3/queries/language.js';
|
||||
|
||||
export default function list(pool) {
|
||||
export default function list(provider) {
|
||||
const sql = provider.dialect === 'sqlite3' ? SQLITE_LIST : MSSQL_LIST;
|
||||
/**
|
||||
* Lists all languages.
|
||||
*
|
||||
@@ -11,7 +13,7 @@ export default function list(pool) {
|
||||
* // => [{ id: 1, iso639: 'de' }]
|
||||
*/
|
||||
return async () => {
|
||||
const result = await pool.request().query(LIST);
|
||||
return result.recordset;
|
||||
const result = await provider.query(sql);
|
||||
return result.rows;
|
||||
};
|
||||
}
|
||||
|
||||
49
src/store/runner.js
Normal file
49
src/store/runner.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Resolves the object a store method should query through.
|
||||
*
|
||||
* Converted store modules call `runner.query(sql, params)`. During the
|
||||
* incremental migration (see TODO.md §1) they may still be constructed with a
|
||||
* raw `mssql` pool, or called with a raw `mssql` Transaction as `tx`. This
|
||||
* adapter exposes the provider contract over both so mixed call sites keep
|
||||
* working until every module is converted.
|
||||
*
|
||||
* @param {import('../providers/provider.js').DatabaseProvider} provider
|
||||
* @param {object} [tx] Optional provider runner or legacy mssql Transaction.
|
||||
* @returns {import('../providers/provider.js').DatabaseProvider}
|
||||
*/
|
||||
|
||||
/** Wraps a raw mssql pool/transaction in the provider query contract. */
|
||||
function wrapMssql(target) {
|
||||
return {
|
||||
dialect: 'mssql',
|
||||
async query(sql, params = {}) {
|
||||
const req = target.request();
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
req.input(key, value);
|
||||
}
|
||||
const result = await req.query(sql);
|
||||
const rows = result.recordset ?? [];
|
||||
const rowsAffected = Array.isArray(result.rowsAffected)
|
||||
? result.rowsAffected.reduce((a, b) => a + b, 0)
|
||||
: 0;
|
||||
return { rows, rowsAffected, insertId: null };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function normalize(target) {
|
||||
if (!target) return null;
|
||||
// Provider instances expose `dialect`; legacy mssql pools also have a
|
||||
// `query()` method, so check for the marker before trusting `query`.
|
||||
if (target.dialect) return target;
|
||||
if (typeof target.request === 'function') return wrapMssql(target);
|
||||
if (typeof target.query === 'function') return target;
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function makeRunner(provider, tx) {
|
||||
if (tx && tx !== provider) {
|
||||
return normalize(tx) ?? normalize(provider) ?? provider;
|
||||
}
|
||||
return normalize(provider) ?? provider;
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { CREATE } from '../../providers/mssql/queries/tax.js';
|
||||
import request from '../request.js';
|
||||
import { CREATE as MSSQL_CREATE } from '../../providers/mssql/queries/tax.js';
|
||||
import { CREATE as SQLITE_CREATE } from '../../providers/sqlite3/queries/tax.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function create(pool) {
|
||||
export default function create(provider) {
|
||||
const sql = provider.dialect === 'sqlite3' ? SQLITE_CREATE : MSSQL_CREATE;
|
||||
/**
|
||||
* Creates a new tax rate.
|
||||
*
|
||||
@@ -14,10 +16,7 @@ export default function create(pool) {
|
||||
* // => 1
|
||||
*/
|
||||
return async (rate, tx) => {
|
||||
const result = await request(pool, tx)
|
||||
.input('rate', rate)
|
||||
.query(CREATE);
|
||||
|
||||
return result.recordset[0].id;
|
||||
const result = await makeRunner(provider, tx).query(sql, { rate });
|
||||
return result.rows[0]?.id ?? result.insertId;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import getByRate from './getByRate.js';
|
||||
import create from './create.js';
|
||||
|
||||
export default function ensure(pool) {
|
||||
const get = getByRate(pool);
|
||||
const add = create(pool);
|
||||
export default function ensure(provider) {
|
||||
const get = getByRate(provider);
|
||||
const add = create(provider);
|
||||
/**
|
||||
* Returns the id of a tax rate, creating it if it does not exist.
|
||||
*
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { GET_BY_RATE } from '../../providers/mssql/queries/tax.js';
|
||||
import request from '../request.js';
|
||||
import { GET_BY_RATE as MSSQL_GET } from '../../providers/mssql/queries/tax.js';
|
||||
import { GET_BY_RATE as SQLITE_GET } from '../../providers/sqlite3/queries/tax.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function getByRate(pool) {
|
||||
export default function getByRate(provider) {
|
||||
const sql = provider.dialect === 'sqlite3' ? SQLITE_GET : MSSQL_GET;
|
||||
/**
|
||||
* Returns a tax row by its rate.
|
||||
*
|
||||
@@ -14,10 +16,7 @@ export default function getByRate(pool) {
|
||||
* // => { id: 1, rate: 19 }
|
||||
*/
|
||||
return async (rate, tx) => {
|
||||
const result = await request(pool, tx)
|
||||
.input('rate', rate)
|
||||
.query(GET_BY_RATE);
|
||||
|
||||
return result.recordset[0];
|
||||
const result = await makeRunner(provider, tx).query(sql, { rate });
|
||||
return result.rows[0];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { LIST } from '../../providers/mssql/queries/tax.js';
|
||||
import { LIST as MSSQL_LIST } from '../../providers/mssql/queries/tax.js';
|
||||
import { LIST as SQLITE_LIST } from '../../providers/sqlite3/queries/tax.js';
|
||||
|
||||
export default function list(pool) {
|
||||
export default function list(provider) {
|
||||
const sql = provider.dialect === 'sqlite3' ? SQLITE_LIST : MSSQL_LIST;
|
||||
/**
|
||||
* Lists all tax rates.
|
||||
*
|
||||
@@ -11,7 +13,7 @@ export default function list(pool) {
|
||||
* // => [{ id: 1, rate: 19 }]
|
||||
*/
|
||||
return async () => {
|
||||
const result = await pool.request().query(LIST);
|
||||
return result.recordset;
|
||||
const result = await provider.query(sql);
|
||||
return result.rows;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user