# libdashstore MSSQL schema + domain API for Dash. One class, methods mirror the file path under `src/store/`. **`mssql` is a peer dependency** — the host app (e.g. dashback) owns `ConnectionPool` and `import sql from 'mssql'`. libdashstore store code uses only the passed `pool` (no own `mssql` install at runtime). Scripts here (`init-schema`, `test-api`, …) use `mssql` from `devDependencies`. ```js import sql from 'mssql'; import LibDashStore from 'libdashstore'; const pool = await sql.connect({ /* … */ }); const client = await new LibDashStore(pool); // runs initialize() (ensure schema, no purge) ``` SQL lives in `src/sql/.js`. Schema DDL: `src/schema.js` / `import 'libdashstore/schema'`. ID fields in the JS API use camelCase `…Id` (DB columns stay snake_case). `googleAuthId` is always `google_auth.id` (the logged-in user), not `companyuser.id`. --- ## Schema **Kein Migrations-Code:** Wir gehen von einer frischen DB aus (`npm run reset-db`). Schema nur in `src/schema.js`; Anpassungen an einer laufenden DB manuell per `sqlcmd`, nicht im Repo. | Method | Description | | --- | --- | | `initialize()` | Runs all `SCHEMA_SCRIPTS` (`IF NOT EXISTS`). Does **not** purge. | | `purge()` | Drops all known tables + number procedure. | --- ## `company` | Method | Signature | Returns | | --- | --- | --- | | `create` | `({ iso639, name, taxrate, reducedtaxrate, googleAuthId })` | `{ id, rootCategoryId, languageId, taxrateId, reducedtaxrateId, taxfreerateId }` | | `update` | `(id, { iso639, name, taxrate, reducedtaxrate })` | `{ id, rootCategoryId, languageId, taxrateId, reducedtaxrateId, taxfreerateId }` | | `delete` | `(companyId)` | `void` | | `list` | `(googleAuthId)` | `{ id, name, rootCategoryId, languageId, taxrate, reducedtaxrate, taxfreerate }[]` | ### `create` (transaction) For `POST /api/company`: 1. Fails if `name` is taken (`Company name not available`) 2. Ensures language by `iso639` (get or create) 3. Ensures tax rows for `taxrate`, `reducedtaxrate`, and `0` → `taxfreerateId` 4. Creates root category (`parentId = null`) 5. Inserts company with all FKs set 6. Links creator as `companyuser` (`googleAuthId` → `company_id`) `company.poscustomergroup_id` (optional) points at the `customergroup` used for POS pricing in `getTreeForCompany` articles. ### `list` Returns only companies where `googleAuthId` has a `companyuser` row. ### `update` (transaction) For `PATCH /api/company/:id`: 1. Fails if company missing / name taken by another company 2. Ensures lang + tax rates 3. Updates `name`, `default_language_id`, `taxrate`, `reducedtaxrate` (`taxfreerateId` / root category unchanged) --- ## `google_auth` | Method | Signature | Returns | | --- | --- | --- | | `add` | `(email)` | `id` | | `getByEmail` | `(email)` | `{ id, email, createdAt, selectedCompanyId } \| undefined` | | `listAllowedEmails` | `()` | `{ id, email, createdAt, selectedCompanyId }[]` | | `removeAllowedEmail` | `(email)` | `void` | | `setSelectedCompany` | `(googleAuthId, companyId)` | `void` | Backend mapping: - Login: `getByEmail(email)` - CLI user manager: `listAllowedEmails()` / `removeAllowedEmail(email)` / `add(email)` - `PUT /api/auth/selected-company`: `setSelectedCompany(googleAuthId, companyId)` --- ## `companyuser` | Method | Signature | Returns | | --- | --- | --- | | `add` | `(companyId, googleAuthId)` | `id` | | `getByEmail` | `(email)` | `id[]` | | `delete` | `(id)` | `void` | --- ## `lang` | Method | Signature | Returns | | --- | --- | --- | | `create` | `(iso639)` | `id` | | `getByIso639` | `(iso639)` | `{ id, iso639 } \| undefined` | | `ensure` | `(iso639, tx?)` | `id` | | `list` | `()` | `{ id, iso639 }[]` | | `delete` | `(id)` | `void` | --- ## `tax` | Method | Signature | Returns | | --- | --- | --- | | `create` | `(rate)` | `id` | | `getByRate` | `(rate)` | `{ id, rate } \| undefined` | | `ensure` | `(rate, tx?)` | `id` | | `list` | `()` | `{ id, rate }[]` | --- ## `category` | Method | Signature | Returns | | --- | --- | --- | | `createUnderParent` | `({ googleAuthId, parentId, name })` | `{ id, parentId, sort, name }` | | `delete` | `({ googleAuthId, id })` | `void` — blocked by FK if `categoryarticle` or child categories remain | | `getTreeForCompany` | `(googleAuthId, companyId)` | `{ languageId, tree, articles }` | | `getCategoryArticles` | `(googleAuthId, categoryId)` | `{ id, sku, name, slug, net, taxRate, optionsCount, articleListCount, childCount, thumbW, thumbH, thumbHash }[]` | ### `createUnderParent` (transaction) 1. `parentId` exists, else `Parent not found` 2. Walk ancestors to root; find `company` with `root_category_id` = root 3. `companyuser` for `(googleAuthId, companyId)`, else `Not authorized` 4. Insert `category` (next sibling `sort`) + `categorytext` (`name`, slugified `slug`, company default language) `getTreeForCompany` throws `Not authorized` if `googleAuthId` is not a `companyuser` of `companyId`, and `Company not found` if the company is missing, has no `root_category_id`, or no `default_language_id`. --- ## `article` | Method | Signature | Returns | | --- | --- | --- | | `createUnderParent` | `({ googleAuthId, parentId, name })` | `{ id, parentId, name, sku }` | `parentId` is the category id. Creates `article` + `articletext` (company default language) + `categoryarticle` in one transaction. Allocates `sku` via `getnextnumberwithprefixandpadding` (`type` `sku`; creates `numbers` row with empty prefix if missing). Default unit `pc` is ensured if missing (`unitamount` 1, `weight` 0). Auth via `resolveCategoryAccess(googleAuthId, parentId)` — `Parent not found` / `Not authorized`. --- ## Backend route cheatsheet | Route | Store call | | --- | --- | | `POST /api/company` | `company.create({ iso639, name, taxrate, reducedtaxrate, googleAuthId })` | | `GET /api/company` | `company.list(googleAuthId)` | | `PATCH /api/company/:id` | `company.update(id, { iso639, name, taxrate, reducedtaxrate })` | | `DELETE /api/company/:id` | `company.delete(id)` | | `PUT /api/auth/selected-company` | `google_auth.setSelectedCompany(googleAuthId, companyId)` | | `GET /api/category/tree?companyId=` | `category.getTreeForCompany(googleAuthId, companyId)` | | `POST /api/category` | `category.createUnderParent({ googleAuthId, parentId, name })` | | `DELETE /api/category/:id` | `category.delete({ googleAuthId, id })` | | `POST /api/article` | `article.createUnderParent({ googleAuthId, parentId, name })` | --- ## Scripts ```bash npm run purge-db # drop all tables + procedure (wipe data) npm run reset-db # purge + init-schema (empty DB, fresh schema) npm run init-schema # ensure schema only (does NOT delete data) npm run test:api # interactive API tester npm run schema-graph # regenerate schema-graph.svg ``` Env: `MSSQL_SERVER`, `MSSQL_DATABASE`, `MSSQL_USER`, `MSSQL_PASSWORD`.