u
This commit is contained in:
@@ -345,6 +345,7 @@ Clients subscribe via [`GET /events`](routes/events.md) (`text/event-stream`). A
|
|||||||
| `shipping` | `POST /stock/ship` |
|
| `shipping` | `POST /stock/ship` |
|
||||||
| `stock` | `DELETE /shipment/{id}`, `POST /stock/accept`, `POST /stock/adjust`, `POST /stock/ship` |
|
| `stock` | `DELETE /shipment/{id}`, `POST /stock/accept`, `POST /stock/adjust`, `POST /stock/ship` |
|
||||||
| `subagentschedule` | `POST /subagentschedule`, `DELETE /subagentschedule/{id}`, `PUT /subagentschedule/{id}` |
|
| `subagentschedule` | `POST /subagentschedule`, `DELETE /subagentschedule/{id}`, `PUT /subagentschedule/{id}` |
|
||||||
|
| `user` | `PUT /auth/selected-company`, `POST /company`, `DELETE /company/{id}` |
|
||||||
| `vfs` | `POST /vfs/applets/restore`, `POST /vfs/mkdir`, `POST /vfs/rename`, `POST /vfs/skills/restore`, `POST /vfs/upload`, `DELETE /vfs/{*path}`, `POST /vfs/{*path}`, `PUT /vfs/{*path}` |
|
| `vfs` | `POST /vfs/applets/restore`, `POST /vfs/mkdir`, `POST /vfs/rename`, `POST /vfs/skills/restore`, `POST /vfs/upload`, `DELETE /vfs/{*path}`, `POST /vfs/{*path}`, `PUT /vfs/{*path}` |
|
||||||
| `warehouse` | `POST /warehouse`, `PUT /warehouse/order`, `DELETE /warehouse/{id}`, `PUT /warehouse/{id}` |
|
| `warehouse` | `POST /warehouse`, `PUT /warehouse/order`, `DELETE /warehouse/{id}`, `PUT /warehouse/{id}` |
|
||||||
|
|
||||||
|
|||||||
@@ -478,6 +478,7 @@ _none_
|
|||||||
- Returns: `200 SSE`
|
- Returns: `200 SSE`
|
||||||
|
|
||||||
- Events:
|
- Events:
|
||||||
|
- `user`: `{ selectedCompanyId }`
|
||||||
- `company`: `{ companyId }`
|
- `company`: `{ companyId }`
|
||||||
- `category`: `{}`
|
- `category`: `{}`
|
||||||
- `articles`: `{}`
|
- `articles`: `{}`
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Sets the authenticated user's selected company.
|
|||||||
|
|
||||||
## Signals
|
## Signals
|
||||||
|
|
||||||
On success, emits SSE signal: `company` (see [`GET /events`](../events.md)).
|
On success, emits SSE signals: `user`, `company` (see [`GET /events`](../events.md)).
|
||||||
|
|
||||||
## Response `200`
|
## Response `200`
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ Creates a company and selects it for the current user.
|
|||||||
|
|
||||||
## Signals
|
## Signals
|
||||||
|
|
||||||
On success, emits SSE signal: `company` (see [`GET /events`](../events.md)).
|
On success, emits SSE signals: `user`, `company` (see [`GET /events`](../events.md)).
|
||||||
|
|
||||||
## Response `201`
|
## Response `201`
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Deletes a company.
|
|||||||
|
|
||||||
## Signals
|
## Signals
|
||||||
|
|
||||||
On success, emits SSE signal: `company` (see [`GET /events`](../events.md)).
|
On success, emits SSE signals: `user`, `company` (see [`GET /events`](../events.md)).
|
||||||
|
|
||||||
## Response `204`
|
## Response `204`
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ Subscribe to update signals (SSE)
|
|||||||
|
|
||||||
Opens a Server-Sent Events stream for the authenticated user.
|
Opens a Server-Sent Events stream for the authenticated user.
|
||||||
Can be opened before a company is selected (`selectedCompanyId == null`);
|
Can be opened before a company is selected (`selectedCompanyId == null`);
|
||||||
when a company is selected or changed, the stream receives a `company`
|
when a company is selected or changed, the stream receives `user`
|
||||||
event (`{ companyId }`) and updates its company scope in-place.
|
(`{ selectedCompanyId }`) and `company` (`{ companyId }`) events and
|
||||||
|
updates its company scope in-place.
|
||||||
Clients also receive named signals when another request successfully
|
Clients also receive named signals when another request successfully
|
||||||
mutates shared data for the active company (including the same
|
mutates shared data for the active company (including the same
|
||||||
browser's other tabs).
|
browser's other tabs).
|
||||||
@@ -16,6 +17,7 @@ Use `EventSource` with credentials so the session cookie is sent:
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
const es = new EventSource('https://…/api/events', { withCredentials: true });
|
const es = new EventSource('https://…/api/events', { withCredentials: true });
|
||||||
|
es.addEventListener('user', () => { refetchAuthMe(); });
|
||||||
es.addEventListener('company', () => { refetchCompany(); });
|
es.addEventListener('company', () => { refetchCompany(); });
|
||||||
es.addEventListener('category', () => { refetchCategoryTree(); });
|
es.addEventListener('category', () => { refetchCategoryTree(); });
|
||||||
es.addEventListener('articles', () => { refetchArticleIndex(); });
|
es.addEventListener('articles', () => { refetchArticleIndex(); });
|
||||||
@@ -27,11 +29,13 @@ Keep-alive comments (`: heartbeat`) are sent roughly every 20s.
|
|||||||
## Signals
|
## Signals
|
||||||
|
|
||||||
Every SSE event uses the event name as the signal id; payloads are
|
Every SSE event uses the event name as the signal id; payloads are
|
||||||
JSON and most events send an empty object `{}`. `company` emits
|
JSON and most events send an empty object `{}`. `user` emits
|
||||||
`{ companyId }` and `brand` emits `{ sender, brandId }`.
|
`{ selectedCompanyId }`, `company` emits `{ companyId }`, and `brand`
|
||||||
|
emits `{ sender, brandId }`.
|
||||||
|
|
||||||
| Signal | Payload | Emitted after successful |
|
| Signal | Payload | Emitted after successful |
|
||||||
|--------|---------|--------------------------|
|
|--------|---------|--------------------------|
|
||||||
|
| `user` | `{ selectedCompanyId }` | `PUT /auth/selected-company`, `POST /company`, `DELETE /company/{id}` |
|
||||||
| `company` | `{ companyId }` | `PUT /auth/selected-company`, `POST /company`, `PUT /company/{id}`, `DELETE /company/{id}` |
|
| `company` | `{ companyId }` | `PUT /auth/selected-company`, `POST /company`, `PUT /company/{id}`, `DELETE /company/{id}` |
|
||||||
| `category` | `{}` | `POST /category`, `PATCH /category/{id}`, `PUT /category/order`, `DELETE /category/{id}`, `POST /category/{id}/image`, `DELETE /category/{id}/image`, `PUT /category/{id}/property` |
|
| `category` | `{}` | `POST /category`, `PATCH /category/{id}`, `PUT /category/order`, `DELETE /category/{id}`, `POST /category/{id}/image`, `DELETE /category/{id}/image`, `PUT /category/{id}/property` |
|
||||||
| `articles` | `{}` | `POST /article`, `PATCH /article/{sku}`, `DELETE /article/{sku}`, `PUT /article/{sku}/articles`-style list-level changes, `POST /category/{id}/article` and related article-assigning category mutations |
|
| `articles` | `{}` | `POST /article`, `PATCH /article/{sku}`, `DELETE /article/{sku}`, `PUT /article/{sku}/articles`-style list-level changes, `POST /category/{id}/article` and related article-assigning category mutations |
|
||||||
@@ -63,7 +67,8 @@ runtime (for example `article-42000045` and `modelconversation-123`).
|
|||||||
|
|
||||||
SSE stream (`text/event-stream`). Each signal is an SSE event
|
SSE stream (`text/event-stream`). Each signal is an SSE event
|
||||||
whose name is the signal id. Most signals send `{}` as data;
|
whose name is the signal id. Most signals send `{}` as data;
|
||||||
`company` sends `{ "companyId": 1 }` and `brand` sends
|
`user` sends `{ "selectedCompanyId": 1 }`, `company` sends
|
||||||
|
`{ "companyId": 1 }`, and `brand` sends
|
||||||
`{ "sender": "<x-client-id>", "brandId": 12 }`.
|
`{ "sender": "<x-client-id>", "brandId": 12 }`.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,14 @@ export function clearCachedUser(authId) {
|
|||||||
userCache.delete(authId);
|
userCache.delete(authId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function clearCachedCompanyUsers(companyId) {
|
||||||
|
for (const [k, v] of userCache) {
|
||||||
|
if (v.user?.selectedCompanyId === companyId) {
|
||||||
|
userCache.delete(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function respondWithAuthPayload(res, req, status, body) {
|
function respondWithAuthPayload(res, req, status, body) {
|
||||||
if (req.path === '/api/auth/me') {
|
if (req.path === '/api/auth/me') {
|
||||||
return respondWithAuthenticatedUser(res, status, body);
|
return respondWithAuthenticatedUser(res, status, body);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import * as db from '../../../db/googleAuth.js';
|
import * as db from '../../../db/googleAuth.js';
|
||||||
import { clearCachedUser } from '../../middleware/auth.js';
|
import { clearCachedUser } from '../../middleware/auth.js';
|
||||||
import { setUserCompany } from '../../sse.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers the selected-company route (requires auth middleware).
|
* Registers the selected-company route (requires auth middleware).
|
||||||
@@ -18,7 +17,7 @@ export function register(app, basePath = '') {
|
|||||||
* security:
|
* security:
|
||||||
* - cookieAuth: []
|
* - cookieAuth: []
|
||||||
* x-markdown: auth/selected-company.md
|
* x-markdown: auth/selected-company.md
|
||||||
* x-signals: [company]
|
* x-signals: [user, company]
|
||||||
* requestBody:
|
* requestBody:
|
||||||
* required: true
|
* required: true
|
||||||
* content:
|
* content:
|
||||||
@@ -78,7 +77,6 @@ export function register(app, basePath = '') {
|
|||||||
selectedCompanyId: updated.selectedCompanyId,
|
selectedCompanyId: updated.selectedCompanyId,
|
||||||
};
|
};
|
||||||
|
|
||||||
setUserCompany(req.user.id, updated.selectedCompanyId);
|
|
||||||
res.status(200).json({ user });
|
res.status(200).json({ user });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Set selected company failed:', error);
|
console.error('Set selected company failed:', error);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import JSZip from 'jszip';
|
|||||||
import * as db from '../../db/company.js';
|
import * as db from '../../db/company.js';
|
||||||
import * as googleAuth from '../../db/googleAuth.js';
|
import * as googleAuth from '../../db/googleAuth.js';
|
||||||
import { clearCachedUser } from '../middleware/auth.js';
|
import { clearCachedUser } from '../middleware/auth.js';
|
||||||
import { clearDeletedCompany, emit, setUserCompany } from '../sse.js';
|
import { emit } from '../sse.js';
|
||||||
import { initCompanyVfs } from '../../tools/vfs.js';
|
import { initCompanyVfs } from '../../tools/vfs.js';
|
||||||
import { executeCompanyImport } from '../../services/companyImport.js';
|
import { executeCompanyImport } from '../../services/companyImport.js';
|
||||||
import { executeCompanyExport } from '../../services/companyExport.js';
|
import { executeCompanyExport } from '../../services/companyExport.js';
|
||||||
@@ -138,7 +138,7 @@ export function register(app, basePath = '') {
|
|||||||
* security:
|
* security:
|
||||||
* - cookieAuth: []
|
* - cookieAuth: []
|
||||||
* x-markdown: company/create.md
|
* x-markdown: company/create.md
|
||||||
* x-signals: [company]
|
* x-signals: [user, company]
|
||||||
* requestBody:
|
* requestBody:
|
||||||
* required: true
|
* required: true
|
||||||
* content:
|
* content:
|
||||||
@@ -217,7 +217,6 @@ export function register(app, basePath = '') {
|
|||||||
console.error('Failed to initialize company VFS:', vfsErr);
|
console.error('Failed to initialize company VFS:', vfsErr);
|
||||||
}
|
}
|
||||||
const authRow = await googleAuth.getAllowedEmail(req.user.email);
|
const authRow = await googleAuth.getAllowedEmail(req.user.email);
|
||||||
setUserCompany(req.user.id, authRow.selectedCompanyId);
|
|
||||||
res.status(201).json({ company, user: userPayload(authRow) });
|
res.status(201).json({ company, user: userPayload(authRow) });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const status = companyErrorStatus(error);
|
const status = companyErrorStatus(error);
|
||||||
@@ -895,7 +894,7 @@ export function register(app, basePath = '') {
|
|||||||
* security:
|
* security:
|
||||||
* - cookieAuth: []
|
* - cookieAuth: []
|
||||||
* x-markdown: company/delete.md
|
* x-markdown: company/delete.md
|
||||||
* x-signals: [company]
|
* x-signals: [user, company]
|
||||||
* parameters:
|
* parameters:
|
||||||
* - in: path
|
* - in: path
|
||||||
* name: id
|
* name: id
|
||||||
@@ -960,7 +959,6 @@ export function register(app, basePath = '') {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await db.deleteCompany(parsedId.companyId);
|
await db.deleteCompany(parsedId.companyId);
|
||||||
clearDeletedCompany(parsedId.companyId);
|
|
||||||
res.sendStatus(204);
|
res.sendStatus(204);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Delete company failed:', error);
|
console.error('Delete company failed:', error);
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ export function register(app, basePath = '') {
|
|||||||
* description: |
|
* description: |
|
||||||
* Opens a Server-Sent Events stream for the authenticated user.
|
* Opens a Server-Sent Events stream for the authenticated user.
|
||||||
* Can be opened before a company is selected (`selectedCompanyId == null`);
|
* Can be opened before a company is selected (`selectedCompanyId == null`);
|
||||||
* when a company is selected or changed, the stream receives a `company`
|
* when a company is selected or changed, the stream receives `user`
|
||||||
* event (`{ companyId }`) and updates its company scope in-place.
|
* (`{ selectedCompanyId }`) and `company` (`{ companyId }`) events and
|
||||||
|
* updates its company scope in-place.
|
||||||
* Clients also receive named signals when another request successfully
|
* Clients also receive named signals when another request successfully
|
||||||
* mutates shared data for the active company (including the same
|
* mutates shared data for the active company (including the same
|
||||||
* browser's other tabs).
|
* browser's other tabs).
|
||||||
@@ -25,6 +26,7 @@ export function register(app, basePath = '') {
|
|||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* const es = new EventSource('https://…/api/events', { withCredentials: true });
|
* const es = new EventSource('https://…/api/events', { withCredentials: true });
|
||||||
|
* es.addEventListener('user', () => { refetchAuthMe(); });
|
||||||
* es.addEventListener('company', () => { refetchCompany(); });
|
* es.addEventListener('company', () => { refetchCompany(); });
|
||||||
* es.addEventListener('category', () => { refetchCategoryTree(); });
|
* es.addEventListener('category', () => { refetchCategoryTree(); });
|
||||||
* es.addEventListener('articles', () => { refetchArticleIndex(); });
|
* es.addEventListener('articles', () => { refetchArticleIndex(); });
|
||||||
@@ -36,11 +38,13 @@ export function register(app, basePath = '') {
|
|||||||
* ## Signals
|
* ## Signals
|
||||||
*
|
*
|
||||||
* Every SSE event uses the event name as the signal id; payloads are
|
* Every SSE event uses the event name as the signal id; payloads are
|
||||||
* JSON and most events send an empty object `{}`. `company` emits
|
* JSON and most events send an empty object `{}`. `user` emits
|
||||||
* `{ companyId }` and `brand` emits `{ sender, brandId }`.
|
* `{ selectedCompanyId }`, `company` emits `{ companyId }`, and `brand`
|
||||||
|
* emits `{ sender, brandId }`.
|
||||||
*
|
*
|
||||||
* | Signal | Payload | Emitted after successful |
|
* | Signal | Payload | Emitted after successful |
|
||||||
* |--------|---------|--------------------------|
|
* |--------|---------|--------------------------|
|
||||||
|
* | `user` | `{ selectedCompanyId }` | `PUT /auth/selected-company`, `POST /company`, `DELETE /company/{id}` |
|
||||||
* | `company` | `{ companyId }` | `PUT /auth/selected-company`, `POST /company`, `PUT /company/{id}`, `DELETE /company/{id}` |
|
* | `company` | `{ companyId }` | `PUT /auth/selected-company`, `POST /company`, `PUT /company/{id}`, `DELETE /company/{id}` |
|
||||||
* | `category` | `{}` | `POST /category`, `PATCH /category/{id}`, `PUT /category/order`, `DELETE /category/{id}`, `POST /category/{id}/image`, `DELETE /category/{id}/image`, `PUT /category/{id}/property` |
|
* | `category` | `{}` | `POST /category`, `PATCH /category/{id}`, `PUT /category/order`, `DELETE /category/{id}`, `POST /category/{id}/image`, `DELETE /category/{id}/image`, `PUT /category/{id}/property` |
|
||||||
* | `articles` | `{}` | `POST /article`, `PATCH /article/{sku}`, `DELETE /article/{sku}`, `PUT /article/{sku}/articles`-style list-level changes, `POST /category/{id}/article` and related article-assigning category mutations |
|
* | `articles` | `{}` | `POST /article`, `PATCH /article/{sku}`, `DELETE /article/{sku}`, `PUT /article/{sku}/articles`-style list-level changes, `POST /category/{id}/article` and related article-assigning category mutations |
|
||||||
@@ -70,6 +74,8 @@ export function register(app, basePath = '') {
|
|||||||
* - cookieAuth: []
|
* - cookieAuth: []
|
||||||
* x-markdown: events.md
|
* x-markdown: events.md
|
||||||
* x-sse-events:
|
* x-sse-events:
|
||||||
|
* - name: user
|
||||||
|
* payload: "{ selectedCompanyId }"
|
||||||
* - name: company
|
* - name: company
|
||||||
* payload: "{ companyId }"
|
* payload: "{ companyId }"
|
||||||
* - name: category
|
* - name: category
|
||||||
@@ -105,13 +111,17 @@ export function register(app, basePath = '') {
|
|||||||
* description: |
|
* description: |
|
||||||
* SSE stream (`text/event-stream`). Each signal is an SSE event
|
* SSE stream (`text/event-stream`). Each signal is an SSE event
|
||||||
* whose name is the signal id. Most signals send `{}` as data;
|
* whose name is the signal id. Most signals send `{}` as data;
|
||||||
* `company` sends `{ "companyId": 1 }` and `brand` sends
|
* `user` sends `{ "selectedCompanyId": 1 }`, `company` sends
|
||||||
|
* `{ "companyId": 1 }`, and `brand` sends
|
||||||
* `{ "sender": "<x-client-id>", "brandId": 12 }`.
|
* `{ "sender": "<x-client-id>", "brandId": 12 }`.
|
||||||
* content:
|
* content:
|
||||||
* text/event-stream:
|
* text/event-stream:
|
||||||
* schema:
|
* schema:
|
||||||
* type: string
|
* type: string
|
||||||
* example: |
|
* example: |
|
||||||
|
* event: user
|
||||||
|
* data: {"selectedCompanyId":1}
|
||||||
|
*
|
||||||
* event: company
|
* event: company
|
||||||
* data: {"companyId":1}
|
* data: {"companyId":1}
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -45,36 +45,41 @@ export function subscribe(res, { companyId = null, userId, clientId }, req) {
|
|||||||
/**
|
/**
|
||||||
* Updates the active company scope for all open SSE streams of a user
|
* Updates the active company scope for all open SSE streams of a user
|
||||||
* (e.g. when the user selects or switches company on a pre-company stream)
|
* (e.g. when the user selects or switches company on a pre-company stream)
|
||||||
* and emits a `company` event to those streams.
|
* and emits `user` and `company` events to those streams.
|
||||||
* @param {number} userId
|
* @param {number} userId
|
||||||
* @param {number|null} companyId
|
* @param {number|null} companyId
|
||||||
*/
|
*/
|
||||||
export function setUserCompany(userId, companyId) {
|
export function setUserCompany(userId, companyId) {
|
||||||
if (userId == null) return;
|
if (userId == null) return;
|
||||||
const nextCompanyId = companyId ?? null;
|
const nextCompanyId = companyId ?? null;
|
||||||
const frame = `event: company\ndata: ${JSON.stringify({ companyId: nextCompanyId })}\n\n`;
|
const userFrame = `event: user\ndata: ${JSON.stringify({ selectedCompanyId: nextCompanyId })}\n\n`;
|
||||||
|
const companyFrame = `event: company\ndata: ${JSON.stringify({ companyId: nextCompanyId })}\n\n`;
|
||||||
for (const client of clients) {
|
for (const client of clients) {
|
||||||
if (client.userId !== userId) continue;
|
if (client.userId !== userId) continue;
|
||||||
client.companyId = nextCompanyId;
|
client.companyId = nextCompanyId;
|
||||||
if (!client.res.writableEnded) {
|
if (!client.res.writableEnded) {
|
||||||
client.res.write(frame);
|
client.res.write(userFrame);
|
||||||
|
client.res.write(companyFrame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears a deleted company from any open SSE streams bound to it
|
* Clears a deleted company from any open SSE streams bound to it
|
||||||
* and emits a `company` event (`{ companyId: null }`) to those streams.
|
* and emits `user` (`{ selectedCompanyId: null }`) and `company` (`{ companyId: null }`)
|
||||||
|
* events to those streams.
|
||||||
* @param {number} companyId
|
* @param {number} companyId
|
||||||
*/
|
*/
|
||||||
export function clearDeletedCompany(companyId) {
|
export function clearDeletedCompany(companyId) {
|
||||||
if (companyId == null) return;
|
if (companyId == null) return;
|
||||||
const frame = `event: company\ndata: ${JSON.stringify({ companyId: null })}\n\n`;
|
const userFrame = `event: user\ndata: ${JSON.stringify({ selectedCompanyId: null })}\n\n`;
|
||||||
|
const companyFrame = `event: company\ndata: ${JSON.stringify({ companyId: null })}\n\n`;
|
||||||
for (const client of clients) {
|
for (const client of clients) {
|
||||||
if (client.companyId !== companyId) continue;
|
if (client.companyId !== companyId) continue;
|
||||||
client.companyId = null;
|
client.companyId = null;
|
||||||
if (!client.res.writableEnded) {
|
if (!client.res.writableEnded) {
|
||||||
client.res.write(frame);
|
client.res.write(userFrame);
|
||||||
|
client.res.write(companyFrame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import env from '../config/env.js';
|
import env from '../config/env.js';
|
||||||
import { getProvider } from '../db/store.js';
|
import { getProvider } from '../db/store.js';
|
||||||
|
import { clearCachedUser } from '../api/middleware/auth.js';
|
||||||
|
import { setUserCompany } from '../api/sse.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns dialect ('sqlite' or 'mssql')
|
* Returns dialect ('sqlite' or 'mssql')
|
||||||
@@ -284,6 +286,14 @@ export async function updateRow(tableName, { primaryKey, changes } = {}) {
|
|||||||
const sql = `UPDATE [${tableName}] SET ${setParts.join(', ')} WHERE ${whereParts.join(' AND ')};`;
|
const sql = `UPDATE [${tableName}] SET ${setParts.join(', ')} WHERE ${whereParts.join(' AND ')};`;
|
||||||
const result = await provider.query(sql, params);
|
const result = await provider.query(sql, params);
|
||||||
|
|
||||||
|
if (tableName.toLowerCase() === 'google_auth' && Object.prototype.hasOwnProperty.call(changes, 'selected_company') && primaryKey.id != null) {
|
||||||
|
const userId = Number(primaryKey.id);
|
||||||
|
const rawCompany = changes.selected_company;
|
||||||
|
const companyId = rawCompany === null || rawCompany === undefined || rawCompany === '' ? null : Number(rawCompany);
|
||||||
|
clearCachedUser(userId);
|
||||||
|
setUserCompany(userId, companyId);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
rowsAffected: result.rowsAffected,
|
rowsAffected: result.rowsAffected,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { MssqlProvider } from 'libdashstore/providers/mssql';
|
|||||||
import { BetterSqlite3Provider } from 'libdashstore/providers/better-sqlite3';
|
import { BetterSqlite3Provider } from 'libdashstore/providers/better-sqlite3';
|
||||||
import env from '../config/env.js';
|
import env from '../config/env.js';
|
||||||
import { createLogger, chalk } from '../util/logger.js';
|
import { createLogger, chalk } from '../util/logger.js';
|
||||||
|
import { clearCachedCompanyUsers, clearCachedUser } from '../api/middleware/auth.js';
|
||||||
|
import { clearDeletedCompany, setUserCompany } from '../api/sse.js';
|
||||||
|
|
||||||
const storeLog = createLogger('libstore');
|
const storeLog = createLogger('libstore');
|
||||||
|
|
||||||
@@ -79,6 +81,22 @@ function wrapStoreWithTiming(rawStore) {
|
|||||||
try {
|
try {
|
||||||
const result = await fn(...args);
|
const result = await fn(...args);
|
||||||
storeLog.debug(`${namespace}.${method} ${chalk.gray(`(${Date.now() - start}ms)`)}`);
|
storeLog.debug(`${namespace}.${method} ${chalk.gray(`(${Date.now() - start}ms)`)}`);
|
||||||
|
if (namespace === 'google_auth' && method === 'setSelectedCompany') {
|
||||||
|
const [googleAuthId, companyId] = args;
|
||||||
|
if (googleAuthId != null) {
|
||||||
|
const normalizedUserId = Number(googleAuthId);
|
||||||
|
const normalizedCompanyId = companyId != null ? Number(companyId) : null;
|
||||||
|
clearCachedUser(normalizedUserId);
|
||||||
|
setUserCompany(normalizedUserId, normalizedCompanyId);
|
||||||
|
}
|
||||||
|
} else if (namespace === 'company' && method === 'delete') {
|
||||||
|
const [companyId] = args;
|
||||||
|
if (companyId != null) {
|
||||||
|
const normalizedCompanyId = Number(companyId);
|
||||||
|
clearCachedCompanyUsers(normalizedCompanyId);
|
||||||
|
clearDeletedCompany(normalizedCompanyId);
|
||||||
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const isNotFound = /not found/i.test(err?.message);
|
const isNotFound = /not found/i.test(err?.message);
|
||||||
|
|||||||
Reference in New Issue
Block a user