# libdashstore
MSSQL schema + domain API for Dash. One class, methods mirror the file path under `src/store/`.
```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)
All category mutations use `resolveCategoryAccess(googleAuthId, categoryId)` internally. Root category cannot be deleted.
`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`.
---
## 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 })` |
---
## 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`.