diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e5ead61
--- /dev/null
+++ b/README.md
@@ -0,0 +1,162 @@
+# 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'`.
+
+---
+
+## Schema
+
+| 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 })` | result |
+| `update` | `(id, { iso639, name, taxrate, reducedtaxrate })` | result |
+| `delete` | `(companyId)` | `void` |
+| `list` | `()` | `result[]` |
+
+### result (`create` / `update` / `list` items)
+
+```ts
+{
+ id: number // company id
+ root_category_id: number
+ language_id: number // language.id for iso639
+ taxrate: number // tax.id
+ reducedtaxrate: number // tax.id
+ taxfreerate: number // tax.id (rate 0; unchanged on update)
+}
+```
+
+### `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` → `taxfreerate`
+4. Creates root category (`parent_id = null`)
+5. Inserts company with all FKs set
+
+### `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`
+ (`taxfreerate` / root category unchanged)
+
+---
+
+## `google_auth`
+
+| Method | Signature | Returns |
+| --- | --- | --- |
+| `add` | `(email)` | `number` (auth id) |
+| `getById` | `(id)` | `row \| undefined` |
+| `getByEmail` | `(email)` | `row \| undefined` |
+| `setSelectedCompany` | `(authId, companyId)` | `void` |
+| `list` | `()` | `row[]` |
+| `del` | `(id)` | `void` |
+
+### Row shape
+
+```ts
+{
+ id: number
+ email: string
+ created_at: Date
+ selected_company: number | null
+}
+```
+
+Backend mapping:
+
+- Login: `getByEmail(email)` ← `getAllowedEmail`
+- Auth middleware: `getById(id)` ← `getAllowedEmailById`
+- `PUT /api/auth/selected-company`: `setSelectedCompany(authId, companyId)`
+
+---
+
+## `companyuser`
+
+| Method | Signature | Returns |
+| --- | --- | --- |
+| `add` | `(companyId, authId)` | `number` (companyuser id) |
+| `getByEmail` | `(email)` | `number[]` (companyuser ids) |
+| `delete` | `(id)` | `void` |
+
+---
+
+## `lang`
+
+| Method | Signature | Returns |
+| --- | --- | --- |
+| `create` | `(iso639)` | `number` |
+| `getByIso639` | `(iso639)` | `{ id, iso639 } \| undefined` |
+| `ensure` | `(iso639, tx?)` | `number` (existing or new id) |
+| `list` | `()` | `{ id, iso639 }[]` |
+| `delete` | `(id)` | `void` |
+
+---
+
+## `tax`
+
+| Method | Signature | Returns |
+| --- | --- | --- |
+| `create` | `(rate)` | `number` |
+| `getByRate` | `(rate)` | `{ id, rate } \| undefined` |
+| `ensure` | `(rate, tx?)` | `number` (existing or new id) |
+| `list` | `()` | `{ id, rate }[]` |
+
+---
+
+## `category`
+
+| Method | Signature | Returns |
+| --- | --- | --- |
+| `create` | `({ parent_id, sort })` | `number` |
+| `updateParentId` | `(id, parentId)` | `void` |
+| `delete` | `(id)` | `void` |
+| `getTree` | `(rootId)` | `{ id, parent_id, sort }[]` (flat CTE result) |
+
+---
+
+## Backend route cheatsheet
+
+| Route | Store call |
+| --- | --- |
+| `POST /api/company` | `company.create({ iso639, name, taxrate, reducedtaxrate })` |
+| `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(authId, companyId)` |
+
+---
+
+## Scripts
+
+```bash
+npm run init-schema # apply SCHEMA_SCRIPTS via .env
+npm run test:api # interactive API tester
+npm run schema-graph # regenerate schema-graph.svg
+```
+
+Env: `MSSQL_SERVER`, `MSSQL_DATABASE`, `MSSQL_USER`, `MSSQL_PASSWORD`.
diff --git a/scripts/test-api.js b/scripts/test-api.js
index 06d70fd..074d407 100644
--- a/scripts/test-api.js
+++ b/scripts/test-api.js
@@ -70,14 +70,10 @@ const MENU = {
company: {
label: 'company',
items: [
- { value: 'company.create', label: 'create', hint: 'iso639,name,taxrate,reducedtaxrate → id' },
- { value: 'company.update', label: 'update', hint: 'id + same fields' },
- { value: 'company.list', label: 'list', hint: '→ rows' },
- { value: 'company.getById', label: 'getById', hint: 'id → row' },
- { value: 'company.setDefaultLang', label: 'setDefaultLang', hint: 'companyId, langId' },
- { value: 'company.setRootCategory', label: 'setRootCategory', hint: 'companyId, categoryId' },
+ { value: 'company.create', label: 'create', hint: 'iso639,name,taxes → result' },
+ { value: 'company.update', label: 'update', hint: 'id + fields → result' },
+ { value: 'company.list', label: 'list', hint: '→ result[]' },
{ value: 'company.delete', label: 'delete', hint: 'companyId' },
- { value: 'company.getByCompanyUserId', label: 'getByCompanyUserId', hint: 'id → tuple' },
],
},
lang: {
@@ -183,12 +179,13 @@ async function runMethod(client, action) {
const reducedtaxrate = await askNumber('Reduced tax rate', '7');
if (reducedtaxrate == null) return;
- const companyId = await client.company.create({
+ const created = await client.company.create({
iso639,
name,
taxrate,
reducedtaxrate,
});
+ const companyId = created.id;
const auths = await client.google_auth.list();
const existingAuth = auths.find((row) => row.email === email);
@@ -202,7 +199,7 @@ async function runMethod(client, action) {
return [
'created',
{
- companyId,
+ ...created,
companyUserId,
authId,
email,
@@ -230,7 +227,7 @@ async function runMethod(client, action) {
const reducedtaxrate = await askNumber('reducedtaxrate', '7');
if (reducedtaxrate == null) return;
return [
- 'id',
+ 'result',
await client.company.create({ iso639, name, taxrate, reducedtaxrate }),
];
}
@@ -251,37 +248,18 @@ async function runMethod(client, action) {
if (taxrate == null) return;
const reducedtaxrate = await askNumber('reducedtaxrate', '7');
if (reducedtaxrate == null) return;
- await client.company.update(Number(id), {
- iso639,
- name,
- taxrate,
- reducedtaxrate,
- });
- return ['ok', true];
+ return [
+ 'result',
+ await client.company.update(Number(id), {
+ iso639,
+ name,
+ taxrate,
+ reducedtaxrate,
+ }),
+ ];
}
case 'company.list': {
- return ['rows', await client.company.list()];
- }
- case 'company.getById': {
- const id = await askInt('Company id');
- if (id == null) return;
- return ['row', await client.company.getById(Number(id))];
- }
- case 'company.setDefaultLang': {
- const companyId = await askInt('Company id');
- if (companyId == null) return;
- const languageId = await askInt('Language id');
- if (languageId == null) return;
- await client.company.setDefaultLang(Number(companyId), Number(languageId));
- return ['ok', true];
- }
- case 'company.setRootCategory': {
- const companyId = await askInt('Company id');
- if (companyId == null) return;
- const categoryId = await askInt('Category id');
- if (categoryId == null) return;
- await client.company.setRootCategory(Number(companyId), Number(categoryId));
- return ['ok', true];
+ return ['result[]', await client.company.list()];
}
case 'company.delete': {
const companyId = await askInt('Company id');
@@ -289,14 +267,6 @@ async function runMethod(client, action) {
await client.company.delete(Number(companyId));
return ['ok', true];
}
- case 'company.getByCompanyUserId': {
- const id = await askInt('Company user id');
- if (id == null) return;
- return [
- '[id, defaultLangId, rootCategoryId, name]',
- await client.company.getByCompanyUserId(Number(id)),
- ];
- }
case 'tax.create': {
const rate = await askNumber('rate', '19');
if (rate == null) return;
diff --git a/src/index.js b/src/index.js
index 7424ede..7b9a10b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,13 +1,8 @@
import companyuserGetByEmail from './store/companyuser/getByEmail.js';
import companyuserAdd from './store/companyuser/add.js';
import companyuserDelete from './store/companyuser/delete.js';
-import companyGetByCompanyUserId from './store/company/getByCompanyUserId.js';
-import companyGetById from './store/company/getById.js';
-import companyGetByName from './store/company/getByName.js';
import companyCreate from './store/company/create.js';
import companyUpdate from './store/company/update.js';
-import companySetDefaultLang from './store/company/setDefaultLang.js';
-import companySetRootCategory from './store/company/setRootCategory.js';
import companyDelete from './store/company/delete.js';
import companyList from './store/company/list.js';
import categoryGetTree from './store/category/getTree.js';
@@ -43,13 +38,8 @@ export default class LibDashStore {
};
this.company = {
- getByCompanyUserId: companyGetByCompanyUserId(pool),
- getById: companyGetById(pool),
- getByName: companyGetByName(pool),
create: companyCreate(pool),
update: companyUpdate(pool),
- setDefaultLang: companySetDefaultLang(pool),
- setRootCategory: companySetRootCategory(pool),
delete: companyDelete(pool),
list: companyList(pool),
};
diff --git a/src/sql/company.js b/src/sql/company.js
index d228180..daa1fc4 100644
--- a/src/sql/company.js
+++ b/src/sql/company.js
@@ -1,14 +1,3 @@
-export const GET_BY_COMPANY_USER_ID = `
-SELECT
- c.id,
- c.default_language_id,
- c.root_category_id,
- c.name
-FROM company c
-INNER JOIN companyuser cu ON cu.company_id = c.id
-WHERE cu.id = @companyUserId
-`;
-
export const GET_BY_NAME = `
SELECT id, name, root_category_id, default_language_id, taxrate, reducedtaxrate, taxfreerate
FROM company
@@ -52,22 +41,16 @@ WHERE id = @id
`;
export const LIST = `
-SELECT id, name, root_category_id, default_language_id, taxrate, reducedtaxrate, taxfreerate
+SELECT
+ id,
+ root_category_id,
+ default_language_id AS language_id,
+ taxrate,
+ reducedtaxrate,
+ taxfreerate
FROM company
`;
-export const SET_DEFAULT_LANG = `
-UPDATE company
-SET default_language_id = @languageId
-WHERE id = @companyId
-`;
-
-export const SET_ROOT_CATEGORY = `
-UPDATE company
-SET root_category_id = @categoryId
-WHERE id = @companyId
-`;
-
export const DELETE = `
DELETE FROM company
WHERE id = @companyId
diff --git a/src/store/company/create.js b/src/store/company/create.js
index e0bc3ea..ce90c96 100644
--- a/src/store/company/create.js
+++ b/src/store/company/create.js
@@ -43,7 +43,15 @@ export default function create(pool) {
.query(CREATE);
await tx.commit();
- return result.recordset[0].id;
+
+ return {
+ id: result.recordset[0].id,
+ root_category_id: rootCategoryId,
+ language_id: languageId,
+ taxrate: taxrateId,
+ reducedtaxrate: reducedtaxrateId,
+ taxfreerate: taxfreerateId,
+ };
} catch (err) {
try {
await tx.rollback();
diff --git a/src/store/company/getByCompanyUserId.js b/src/store/company/getByCompanyUserId.js
deleted file mode 100644
index 996933e..0000000
--- a/src/store/company/getByCompanyUserId.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import sql from 'mssql';
-import { GET_BY_COMPANY_USER_ID } from '../../sql/company.js';
-
-export default function getByCompanyUserId(pool) {
- return async (companyUserId) => {
- const result = await pool
- .request()
- .input('companyUserId', sql.Int, companyUserId)
- .query(GET_BY_COMPANY_USER_ID);
-
- const row = result.recordset[0];
- if (!row) return;
-
- return [row.id, row.default_language_id, row.root_category_id, row.name];
- };
-}
diff --git a/src/store/company/setDefaultLang.js b/src/store/company/setDefaultLang.js
deleted file mode 100644
index 2ad3598..0000000
--- a/src/store/company/setDefaultLang.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import sql from 'mssql';
-import { SET_DEFAULT_LANG } from '../../sql/company.js';
-
-export default function setDefaultLang(pool) {
- return async (companyId, languageId) => {
- await pool
- .request()
- .input('companyId', sql.Int, companyId)
- .input('languageId', sql.Int, languageId)
- .query(SET_DEFAULT_LANG);
- };
-}
diff --git a/src/store/company/setRootCategory.js b/src/store/company/setRootCategory.js
deleted file mode 100644
index 5be9e59..0000000
--- a/src/store/company/setRootCategory.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import sql from 'mssql';
-import { SET_ROOT_CATEGORY } from '../../sql/company.js';
-
-export default function setRootCategory(pool) {
- return async (companyId, categoryId) => {
- await pool
- .request()
- .input('companyId', sql.Int, companyId)
- .input('categoryId', sql.Int, categoryId)
- .query(SET_ROOT_CATEGORY);
- };
-}
diff --git a/src/store/company/update.js b/src/store/company/update.js
index 09e2db3..e3bd9c8 100644
--- a/src/store/company/update.js
+++ b/src/store/company/update.js
@@ -42,6 +42,15 @@ export default function update(pool) {
.query(UPDATE);
await tx.commit();
+
+ return {
+ id,
+ root_category_id: company.root_category_id,
+ language_id: languageId,
+ taxrate: taxrateId,
+ reducedtaxrate: reducedtaxrateId,
+ taxfreerate: company.taxfreerate,
+ };
} catch (err) {
try {
await tx.rollback();