This commit is contained in:
seb
2026-07-31 05:57:23 +02:00
parent b99c3a9ec7
commit 02bca8e236
14 changed files with 93 additions and 132 deletions

View File

@@ -12,6 +12,8 @@ const client = await new LibDashStore(pool); // runs initialize() (ensure schema
SQL lives in `src/sql/<table>.js`. Schema DDL: `src/schema.js` / `import 'libdashstore/schema'`.
ID fields in the JS API use camelCase `…Id` (DB columns stay snake_case).
---
## Schema
@@ -27,11 +29,10 @@ SQL lives in `src/sql/<table>.js`. Schema DDL: `src/schema.js` / `import 'libdas
| Method | Signature | Returns |
| --- | --- | --- |
| `create` | `({ iso639, name, taxrate, reducedtaxrate })` | `{ id, root_category_id, language_id, taxrate_id, reducedtaxrate_id, taxfreerate_id }` |
| `update` | `(id, { iso639, name, taxrate, reducedtaxrate })` | `{ id, root_category_id, language_id, taxrate_id, reducedtaxrate_id, taxfreerate_id }` |
| `create` | `({ iso639, name, taxrate, reducedtaxrate })` | `{ id, rootCategoryId, languageId, taxrateId, reducedtaxrateId, taxfreerateId }` |
| `update` | `(id, { iso639, name, taxrate, reducedtaxrate })` | `{ id, rootCategoryId, languageId, taxrateId, reducedtaxrateId, taxfreerateId }` |
| `delete` | `(companyId)` | `void` |
| `list` | `()` | `{ id, root_category_id, language_id, taxrate_id, reducedtaxrate_id, taxfreerate_id }[]` |
| `list` | `()` | `{ id, rootCategoryId, languageId, taxrateId, reducedtaxrateId, taxfreerateId }[]` |
### `create` (transaction)
@@ -39,8 +40,8 @@ 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_id`
4. Creates root category (`parent_id = null`)
3. Ensures tax rows for `taxrate`, `reducedtaxrate`, and `0` → `taxfreerateId`
4. Creates root category (`parentId = null`)
5. Inserts company with all FKs set
### `update` (transaction)
@@ -50,7 +51,7 @@ 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_id` / root category unchanged)
(`taxfreerateId` / root category unchanged)
---
@@ -58,28 +59,16 @@ For `PATCH /api/company/:id`:
| Method | Signature | Returns |
| --- | --- | --- |
| `add` | `(email)` | `number` (auth id) |
| `getById` | `(id)` | `row \| undefined` |
| `getByEmail` | `(email)` | `row \| undefined` |
| `add` | `(email)` | `id` |
| `getByEmail` | `(email)` | `{ id, email, createdAt, selectedCompanyId } \| undefined` |
| `listAllowedEmails` | `()` | `{ id, email, createdAt, selectedCompanyId }[]` |
| `removeAllowedEmail` | `(email)` | `void` |
| `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`
- Login: `getByEmail(email)`
- CLI user manager: `listAllowedEmails()` / `removeAllowedEmail(email)` / `add(email)`
- `PUT /api/auth/selected-company`: `setSelectedCompany(authId, companyId)`
---
@@ -88,8 +77,8 @@ Backend mapping:
| Method | Signature | Returns |
| --- | --- | --- |
| `add` | `(companyId, authId)` | `number` (companyuser id) |
| `getByEmail` | `(email)` | `number[]` (companyuser ids) |
| `add` | `(companyId, authId)` | `id` |
| `getByEmail` | `(email)` | `id[]` |
| `delete` | `(id)` | `void` |
---
@@ -98,9 +87,9 @@ Backend mapping:
| Method | Signature | Returns |
| --- | --- | --- |
| `create` | `(iso639)` | `number` |
| `create` | `(iso639)` | `id` |
| `getByIso639` | `(iso639)` | `{ id, iso639 } \| undefined` |
| `ensure` | `(iso639, tx?)` | `number` (existing or new id) |
| `ensure` | `(iso639, tx?)` | `id` |
| `list` | `()` | `{ id, iso639 }[]` |
| `delete` | `(id)` | `void` |
@@ -110,9 +99,9 @@ Backend mapping:
| Method | Signature | Returns |
| --- | --- | --- |
| `create` | `(rate)` | `number` |
| `create` | `(rate)` | `id` |
| `getByRate` | `(rate)` | `{ id, rate } \| undefined` |
| `ensure` | `(rate, tx?)` | `number` (existing or new id) |
| `ensure` | `(rate, tx?)` | `id` |
| `list` | `()` | `{ id, rate }[]` |
---
@@ -121,10 +110,10 @@ Backend mapping:
| Method | Signature | Returns |
| --- | --- | --- |
| `create` | `({ parent_id, sort })` | `number` |
| `create` | `({ parentId, sort })` | `id` |
| `updateParentId` | `(id, parentId)` | `void` |
| `delete` | `(id)` | `void` |
| `getTree` | `(rootId)` | `{ id, parent_id, sort }[]` (flat CTE result) |
| `getTree` | `(rootId)` | `{ id, parentId, sort }[]` |
---

View File

@@ -95,7 +95,7 @@ const MENU = {
category: {
label: 'category',
items: [
{ value: 'category.create', label: 'create', hint: '{parent_id, sort} → id' },
{ value: 'category.create', label: 'create', hint: '{parentId, sort} → id' },
{ value: 'category.updateParentId', label: 'updateParentId', hint: 'id, parentId' },
{ value: 'category.delete', label: 'delete', hint: 'id' },
{ value: 'category.getTree', label: 'getTree', hint: 'rootId → rows' },
@@ -112,12 +112,11 @@ const MENU = {
google_auth: {
label: 'google_auth',
items: [
{ value: 'google_auth.add', label: 'add', hint: 'email → authId' },
{ value: 'google_auth.getById', label: 'getById', hint: 'id → row' },
{ value: 'google_auth.add', label: 'add', hint: 'email → id' },
{ value: 'google_auth.getByEmail', label: 'getByEmail', hint: 'email → row' },
{ value: 'google_auth.listAllowedEmails', label: 'listAllowedEmails', hint: '→ rows' },
{ value: 'google_auth.removeAllowedEmail', label: 'removeAllowedEmail', hint: 'email' },
{ value: 'google_auth.setSelectedCompany', label: 'setSelectedCompany', hint: 'authId, companyId' },
{ value: 'google_auth.list', label: 'list', hint: '→ rows' },
{ value: 'google_auth.del', label: 'del', hint: 'id' },
],
},
schema: {
@@ -186,13 +185,10 @@ async function runMethod(client, action) {
reducedtaxrate,
});
const companyId = created.id;
const auths = await client.google_auth.list();
const existingAuth = auths.find((row) => row.email === email);
const authId = existingAuth
? existingAuth.id
const existing = await client.google_auth.getByEmail(email);
const authId = existing
? existing.id
: await client.google_auth.add(email);
const companyUserId = await client.companyuser.add(companyId, authId);
await client.google_auth.setSelectedCompany(authId, companyId);
@@ -207,7 +203,6 @@ async function runMethod(client, action) {
iso639,
taxrate,
reducedtaxrate,
authCreated: !existingAuth,
},
];
}
@@ -298,7 +293,7 @@ async function runMethod(client, action) {
return ['ok', true];
}
case 'category.create': {
const parentRaw = await askText('parent_id (null as empty)', {
const parentRaw = await askText('parentId (null as empty)', {
placeholder: '',
validate: (v) =>
v?.trim() && !/^\d+$/.test(v.trim()) ? 'Integer or empty' : undefined,
@@ -306,13 +301,13 @@ async function runMethod(client, action) {
if (parentRaw == null) return;
const sortRaw = await askInt('sort', '0');
if (sortRaw == null) return;
const parent_id = parentRaw === '' ? null : Number(parentRaw);
return ['id', await client.category.create({ parent_id, sort: Number(sortRaw) })];
const parentId = parentRaw === '' ? null : Number(parentRaw);
return ['id', await client.category.create({ parentId, sort: Number(sortRaw) })];
}
case 'category.updateParentId': {
const id = await askInt('Category id');
if (id == null) return;
const parentRaw = await askText('parent_id (null as empty)', {
const parentRaw = await askText('parentId (null as empty)', {
placeholder: '',
validate: (v) =>
v?.trim() && !/^\d+$/.test(v.trim()) ? 'Integer or empty' : undefined,
@@ -360,12 +355,7 @@ async function runMethod(client, action) {
validate: (v) => (!v?.trim() ? 'Required' : undefined),
});
if (email == null) return;
return ['authId', await client.google_auth.add(email)];
}
case 'google_auth.getById': {
const id = await askInt('Auth id');
if (id == null) return;
return ['row', await client.google_auth.getById(Number(id))];
return ['id', await client.google_auth.add(email)];
}
case 'google_auth.getByEmail': {
const email = await askText('Email', {
@@ -375,6 +365,18 @@ async function runMethod(client, action) {
if (email == null) return;
return ['row', await client.google_auth.getByEmail(email)];
}
case 'google_auth.listAllowedEmails': {
return ['rows', await client.google_auth.listAllowedEmails()];
}
case 'google_auth.removeAllowedEmail': {
const email = await askText('Email', {
placeholder: 'user@example.com',
validate: (v) => (!v?.trim() ? 'Required' : undefined),
});
if (email == null) return;
await client.google_auth.removeAllowedEmail(email);
return ['ok', true];
}
case 'google_auth.setSelectedCompany': {
const id = await askInt('Auth id');
if (id == null) return;
@@ -383,15 +385,6 @@ async function runMethod(client, action) {
await client.google_auth.setSelectedCompany(Number(id), Number(companyId));
return ['ok', true];
}
case 'google_auth.list': {
return ['rows', await client.google_auth.list()];
}
case 'google_auth.del': {
const id = await askInt('Auth id');
if (id == null) return;
await client.google_auth.del(Number(id));
return ['ok', true];
}
case 'initialize': {
await client.initialize();
return ['ok', true];

View File

@@ -19,11 +19,10 @@ import taxGetByRate from './store/tax/getByRate.js';
import taxEnsure from './store/tax/ensure.js';
import taxList from './store/tax/list.js';
import googleAuthAdd from './store/google_auth/add.js';
import googleAuthGetById from './store/google_auth/getById.js';
import googleAuthGetByEmail from './store/google_auth/getByEmail.js';
import googleAuthListAllowedEmails from './store/google_auth/listAllowedEmails.js';
import googleAuthRemoveAllowedEmail from './store/google_auth/removeAllowedEmail.js';
import googleAuthSetSelectedCompany from './store/google_auth/setSelectedCompany.js';
import googleAuthList from './store/google_auth/list.js';
import googleAuthDel from './store/google_auth/del.js';
import storePurge from './store/purge.js';
import storeInitialize from './store/initialize.js';
@@ -68,11 +67,10 @@ export default class LibDashStore {
this.google_auth = {
add: googleAuthAdd(pool),
getById: googleAuthGetById(pool),
getByEmail: googleAuthGetByEmail(pool),
listAllowedEmails: googleAuthListAllowedEmails(pool),
removeAllowedEmail: googleAuthRemoveAllowedEmail(pool),
setSelectedCompany: googleAuthSetSelectedCompany(pool),
list: googleAuthList(pool),
del: googleAuthDel(pool),
};
this.purge = storePurge(pool);

View File

@@ -10,7 +10,7 @@ WITH tree AS (
FROM category c
INNER JOIN tree t ON c.parent_id = t.id
)
SELECT id, parent_id, sort
SELECT id, parent_id AS parentId, sort
FROM tree
ORDER BY sort, id
`;

View File

@@ -43,11 +43,11 @@ WHERE id = @id
export const LIST = `
SELECT
id,
root_category_id,
default_language_id AS language_id,
taxrate AS taxrate_id,
reducedtaxrate AS reducedtaxrate_id,
taxfreerate AS taxfreerate_id
root_category_id AS rootCategoryId,
default_language_id AS languageId,
taxrate AS taxrateId,
reducedtaxrate AS reducedtaxrateId,
taxfreerate AS taxfreerateId
FROM company
`;

View File

@@ -4,27 +4,21 @@ OUTPUT INSERTED.id
VALUES (@email)
`;
export const GET_BY_ID = `
SELECT id, email, created_at, selected_company
FROM google_auth
WHERE id = @id
`;
export const GET_BY_EMAIL = `
SELECT id, email, created_at, selected_company
SELECT id, email, created_at AS createdAt, selected_company AS selectedCompanyId
FROM google_auth
WHERE email = @email
`;
export const LIST = `
SELECT id, email, created_at, selected_company
export const LIST_ALLOWED_EMAILS = `
SELECT id, email, created_at AS createdAt, selected_company AS selectedCompanyId
FROM google_auth
ORDER BY id
ORDER BY email
`;
export const DEL = `
export const REMOVE_ALLOWED_EMAIL = `
DELETE FROM google_auth
WHERE id = @id
WHERE email = @email
`;
export const SET_SELECTED_COMPANY = `

View File

@@ -2,10 +2,10 @@ import sql from 'mssql';
import { CREATE } from '../../sql/category.js';
export default function create(pool) {
return async ({ parent_id, sort }) => {
return async ({ parentId, sort }) => {
const result = await pool
.request()
.input('parentId', sql.Int, parent_id)
.input('parentId', sql.Int, parentId)
.input('sort', sql.Int, sort)
.query(CREATE);

View File

@@ -46,11 +46,11 @@ export default function create(pool) {
return {
id: result.recordset[0].id,
root_category_id: rootCategoryId,
language_id: languageId,
taxrate_id: taxrateId,
reducedtaxrate_id: reducedtaxrateId,
taxfreerate_id: taxfreerateId,
rootCategoryId,
languageId,
taxrateId,
reducedtaxrateId,
taxfreerateId,
};
} catch (err) {
try {

View File

@@ -45,11 +45,11 @@ export default function update(pool) {
return {
id,
root_category_id: company.root_category_id,
language_id: languageId,
taxrate_id: taxrateId,
reducedtaxrate_id: reducedtaxrateId,
taxfreerate_id: company.taxfreerate,
rootCategoryId: company.root_category_id,
languageId,
taxrateId,
reducedtaxrateId,
taxfreerateId: company.taxfreerate,
};
} catch (err) {
try {

View File

@@ -1,11 +0,0 @@
import sql from 'mssql';
import { DEL } from '../../sql/google_auth.js';
export default function del(pool) {
return async (id) => {
await pool
.request()
.input('id', sql.Int, id)
.query(DEL);
};
}

View File

@@ -1,13 +0,0 @@
import sql from 'mssql';
import { GET_BY_ID } from '../../sql/google_auth.js';
export default function getById(pool) {
return async (id) => {
const result = await pool
.request()
.input('id', sql.Int, id)
.query(GET_BY_ID);
return result.recordset[0];
};
}

View File

@@ -1,8 +0,0 @@
import { LIST } from '../../sql/google_auth.js';
export default function list(pool) {
return async () => {
const result = await pool.request().query(LIST);
return result.recordset;
};
}

View File

@@ -0,0 +1,8 @@
import { LIST_ALLOWED_EMAILS } from '../../sql/google_auth.js';
export default function listAllowedEmails(pool) {
return async () => {
const result = await pool.request().query(LIST_ALLOWED_EMAILS);
return result.recordset;
};
}

View File

@@ -0,0 +1,11 @@
import sql from 'mssql';
import { REMOVE_ALLOWED_EMAIL } from '../../sql/google_auth.js';
export default function removeAllowedEmail(pool) {
return async (email) => {
await pool
.request()
.input('email', sql.NVarChar(320), email)
.query(REMOVE_ALLOWED_EMAIL);
};
}