diff --git a/docs/api/README.md b/docs/api/README.md index 2822acc..2a91911 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -85,8 +85,9 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route | [`routes/auth/me.md`](routes/auth/me.md) | `GET /auth/me` | | [`routes/auth/selected-company.md`](routes/auth/selected-company.md) | `PUT /auth/selected-company` | | [`routes/brand/list.md`](routes/brand/list.md) | `GET /brand` | -| [`routes/brand/upsert.md`](routes/brand/upsert.md) | `PUT /brand` | +| [`routes/brand/create.md`](routes/brand/create.md) | `POST /brand` | | [`routes/brand/delete.md`](routes/brand/delete.md) | `DELETE /brand/{id}` | +| [`routes/brand/update.md`](routes/brand/update.md) | `PATCH /brand/{id}` | | [`routes/brand/_id/image.delete.md`](routes/brand/_id/image.delete.md) | `DELETE /brand/{id}/image` | | [`routes/brand/_id/image.post.md`](routes/brand/_id/image.post.md) | `POST /brand/{id}/image` | | [`routes/category/create.md`](routes/category/create.md) | `POST /category` | @@ -247,6 +248,7 @@ Clients subscribe via [`GET /events`](routes/events.md) (`text/event-stream`). A |--------|------------| | `article-` | `PUT /article/{sku}/brand`, `PUT /article/{sku}/categories`, `PUT /article/{sku}/eans`, `POST /article/{sku}/image`, `DELETE /article/{sku}/image/{fullHash}`, `PUT /article/{sku}/images/order`, `PUT /article/{sku}/prices`, `POST /article/{sku}/supplier`, `PUT /article/{sku}/supplier/order`, `DELETE /article/{sku}/supplier/{id}`, `PATCH /article/{sku}/supplier/{id}`, `PUT /article/{sku}/track_stock`, `PUT /article/{sku}/weight` | | `articles` | `POST /article`, `DELETE /article/{sku}`, `PATCH /article/{sku}`, `DELETE /category/{id}` | +| `brand` | `POST /brand`, `DELETE /brand/{id}`, `PATCH /brand/{id}`, `DELETE /brand/{id}/image`, `POST /brand/{id}/image` | | `category` | `POST /category`, `PUT /category/order`, `DELETE /category/{id}`, `PATCH /category/{id}`, `DELETE /category/{id}/image`, `POST /category/{id}/image` | | `vfs` | `POST /vfs/mkdir`, `POST /vfs/rename`, `POST /vfs/upload`, `DELETE /vfs/{*path}`, `PUT /vfs/{*path}` | diff --git a/docs/api/routes-all.md b/docs/api/routes-all.md index 4649b3a..1c9cb33 100644 --- a/docs/api/routes-all.md +++ b/docs/api/routes-all.md @@ -1,6 +1,6 @@ # Dash API Routes Overview (All) -Overview of all 204 API endpoints across all modules. +Overview of all 205 API endpoints across all modules. ### Notation @@ -216,12 +216,15 @@ _none_ #### List brands — `GET /brand` _none_ - Returns: `200 { brands: { id: int, name: str, pics: T30[], gsprpinfo?: str|null }[] }` -#### Upsert brand — `PUT /brand` +#### Create brand — `POST /brand` - Params: `body: { name: str, gsprpinfo?: str }` -- Returns: `200 { brand: { id: int, name: str, pics: T30[], gsprpinfo?: str|null } }` +- Returns: `201 { brand: { id: int, name: str, pics: T30[], gsprpinfo?: str|null } }` #### Delete brand — `DELETE /brand/{id}` - Params: `id: int` - Returns: `204 void` +#### Rename brand — `PATCH /brand/{id}` +- Params: `id: int, body: { name?: str, gsprpinfo?: str }` +- Returns: `200 { brand: { id: int, name: str, touchedArticleCount: int, gsprpinfo?: str|null } }` #### Delete brand image — `DELETE /brand/{id}/image` - Params: `id: int` - Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T30[] } }` diff --git a/docs/api/routes.md b/docs/api/routes.md index 975ce17..37886d6 100644 --- a/docs/api/routes.md +++ b/docs/api/routes.md @@ -1,6 +1,6 @@ # Dash API Routes Overview -Overview of all 158 API endpoints across all modules. +Overview of all 159 API endpoints across all modules. ### Notation @@ -199,12 +199,15 @@ _none_ #### List brands — `GET /brand` _none_ - Returns: `200 { brands: { id: int, name: str, pics: T28[], gsprpinfo?: str|null }[] }` -#### Upsert brand — `PUT /brand` +#### Create brand — `POST /brand` - Params: `body: { name: str, gsprpinfo?: str }` -- Returns: `200 { brand: { id: int, name: str, pics: T28[], gsprpinfo?: str|null } }` +- Returns: `201 { brand: { id: int, name: str, pics: T28[], gsprpinfo?: str|null } }` #### Delete brand — `DELETE /brand/{id}` - Params: `id: int` - Returns: `204 void` +#### Rename brand — `PATCH /brand/{id}` +- Params: `id: int, body: { name?: str, gsprpinfo?: str }` +- Returns: `200 { brand: { id: int, name: str, touchedArticleCount: int, gsprpinfo?: str|null } }` #### Delete brand image — `DELETE /brand/{id}/image` - Params: `id: int` - Returns: `200 { brand: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T28[] } }` diff --git a/docs/api/routes/brand/_id/image.delete.md b/docs/api/routes/brand/_id/image.delete.md index 6889aad..3716a8a 100644 --- a/docs/api/routes/brand/_id/image.delete.md +++ b/docs/api/routes/brand/_id/image.delete.md @@ -4,7 +4,7 @@ Protected. Requires a valid session cookie (`credentials: 'include'`). Delete brand image -Removes the brand's image. Sets thumbW, thumbH, thumbHash back to null. +Removes the brand's image. Sets thumbW, thumbH, thumbHash back to null. Emits SSE signal `brand` with payload `{ sender, brandId }`. ## Path parameters @@ -12,6 +12,10 @@ Removes the brand's image. Sets thumbW, thumbH, thumbHash back to null. |-------|----------|------|-------------| | `id` | yes | integer | Brand id | +## Signals + +On success, emits SSE signal: `brand` (see [`GET /events`](../events.md)). + ## Response `200` Image removed diff --git a/docs/api/routes/brand/_id/image.post.md b/docs/api/routes/brand/_id/image.post.md index 6f2f0a2..d6a61f3 100644 --- a/docs/api/routes/brand/_id/image.post.md +++ b/docs/api/routes/brand/_id/image.post.md @@ -5,7 +5,8 @@ Protected. Requires a valid session cookie (`credentials: 'include'`). Add or replace brand image Uploads an image for the brand (AVIF, WebP, or octet-stream). Converted to AVIF full and thumbnail. -Replaces any existing image for this brand. +Replaces any existing image for this brand. Emits SSE signal `brand` +with payload `{ sender, brandId }`. ## Path parameters @@ -22,6 +23,10 @@ Raw binary (`image/webp`). Raw binary (`application/octet-stream`). +## Signals + +On success, emits SSE signal: `brand` (see [`GET /events`](../events.md)). + ## Response `201` Image stored and linked diff --git a/docs/api/routes/brand/upsert.md b/docs/api/routes/brand/create.md similarity index 69% rename from docs/api/routes/brand/upsert.md rename to docs/api/routes/brand/create.md index 933803a..aea6c9e 100644 --- a/docs/api/routes/brand/upsert.md +++ b/docs/api/routes/brand/create.md @@ -1,10 +1,12 @@ -# `PUT /brand` +# `POST /brand` Protected. Requires a valid session cookie (`credentials: 'include'`). -Upsert brand +Create brand -Insert or update brand by `name` for the selected company. `gsprpinfo` optional. +Creates a brand for the selected company. Fails with `Brand already exists` +if the name is taken. Emits SSE signal `brand` with payload +`{ sender, brandId }` (`sender` echoes the caller's `x-client-id`). ## Body (JSON) @@ -21,9 +23,13 @@ Insert or update brand by `name` for the selected company. `gsprpinfo` optional. } ``` -## Response `200` +## Signals -Upserted brand +On success, emits SSE signal: `brand` (see [`GET /events`](../events.md)). + +## Response `201` + +Created brand ```json { @@ -53,6 +59,7 @@ Upserted brand | 401 | Unauthorized — `{"error":"…"}` | | 403 | Not authorized — `{"error":"…"}` | | 404 | Company not found — `{"error":"…"}` | +| 409 | Brand already exists — `{"error":"…"}` | --- diff --git a/docs/api/routes/brand/delete.md b/docs/api/routes/brand/delete.md index aac6a28..6629b3d 100644 --- a/docs/api/routes/brand/delete.md +++ b/docs/api/routes/brand/delete.md @@ -5,7 +5,8 @@ Protected. Requires a valid session cookie (`credentials: 'include'`). Delete brand Deletes the brand for the selected company. Fails with `Brand in use` -if any article still references it. +if any article still references it. Emits SSE signal `brand` +with payload `{ sender, brandId }`. ## Path parameters @@ -14,6 +15,10 @@ if any article still references it. |-------|----------|------|-------------| | `id` | yes | integer | | +## Signals + +On success, emits SSE signal: `brand` (see [`GET /events`](../events.md)). + ## Response `204` Deleted diff --git a/docs/api/routes/brand/update.md b/docs/api/routes/brand/update.md new file mode 100644 index 0000000..245dc9f --- /dev/null +++ b/docs/api/routes/brand/update.md @@ -0,0 +1,65 @@ +# `PATCH /brand/{id}` + +Protected. Requires a valid session cookie (`credentials: 'include'`). + +Rename brand + +Renames/updates a brand by id for the selected company. Fails with +`Brand already exists` if the name is taken by another brand. +When the name changes, linked articles (`brand_id`) get their +`updated_at` bumped; the response reports `touchedArticleCount`. +Emits SSE signal `brand` with payload `{ sender, brandId }` +(`sender` echoes the caller's `x-client-id`). + + +## Path parameters + +| Param | Required | Type | Description | +|-------|----------|------|-------------| +| `id` | yes | integer | | + +## Body (JSON) + +| Field | Required | Type | Description | +|-------|----------|------|-------------| +| `name` | no | string | | +| `gsprpinfo` | no | string | | + +```json +{ + "name": "Acme GmbH" +} +``` + +## Signals + +On success, emits SSE signal: `brand` (see [`GET /events`](../events.md)). + +## Response `200` + +Updated brand + +```json +{ + "brand": { + "id": 1, + "name": "…", + "gsprpinfo": "…", + "touchedArticleCount": 1 + } +} +``` + +## Errors + +| Status | Description | +|--------|-------------| +| 400 | Invalid brand, invalid id, or no company selected — `{"error":"…"}` | +| 401 | Unauthorized — `{"error":"…"}` | +| 403 | Not authorized — `{"error":"…"}` | +| 404 | Brand or company not found — `{"error":"…"}` | +| 409 | Brand already exists — `{"error":"…"}` | + +--- + +_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._ diff --git a/docs/api/routes/events.md b/docs/api/routes/events.md index 3b0cc9a..f32f1c6 100644 --- a/docs/api/routes/events.md +++ b/docs/api/routes/events.md @@ -29,6 +29,12 @@ to the company at connect time). | `category` | `POST /category`, `PATCH /category/{id}`, `PUT /category/order`, `DELETE /category/{id}`, `POST /category/{id}/image`, `DELETE /category/{id}/image` | | `articles` | `POST /article`, `PATCH /article/{sku}`, `DELETE /article/{sku}` | | `article-` | `PUT /article/{sku}/weight`, `PUT /article/{sku}/prices`, `PUT /article/{sku}/eans`, `PUT /article/{sku}/categories`, `PUT /article/{sku}/brand`, `PUT /article/{sku}/images/order`, `POST /article/{sku}/image`, `DELETE /article/{sku}/image/{fullHash}` | +| `brand` | `POST /brand`, `PATCH /brand/{id}`, `DELETE /brand/{id}`, `POST /brand/{id}/image`, `DELETE /brand/{id}/image` | + +The `brand` signal carries a JSON payload `{ sender, brandId }`: +`sender` echoes the caller's `x-client-id` header (empty string if +absent) so the sender can ignore its own event; `brandId` is the +affected brand id (`null` only if unknown). Other signals send `{}`. Signals are only sent when the mutation actually succeeds (2xx). Failed or no-op validation errors do not emit. The `article-` @@ -38,7 +44,8 @@ event name uses the concrete SKU (e.g. `article-42000045`). ## Response `200` SSE stream (`text/event-stream`). Each signal is an SSE event -whose name is the signal id and whose data is `{}`. +whose name is the signal id. Most signals send `{}` as data; +`brand` sends `{ "sender": "", "brandId": 12 }`. ## Errors diff --git a/src/api/routes/brand.js b/src/api/routes/brand.js index 8722b21..e333ee7 100644 --- a/src/api/routes/brand.js +++ b/src/api/routes/brand.js @@ -1,5 +1,6 @@ import express from 'express'; import * as db from '../../db/brand.js'; +import { emit } from '../sse.js'; const AVIF_BODY_LIMIT = 10 * 1024 * 1024; @@ -18,11 +19,20 @@ function brandErrorStatus(error) { if (error.message === 'Company not found') return 404; if (error.message === 'Brand not found') return 404; if (error.message === 'Invalid brand') return 400; + if (error.message === 'Brand already exists') return 409; if (error.message === 'Brand in use') return 409; if (error.message === 'Image not found') return 404; return 500; } +/** Notify SSE subscribers after brand change; payload is `{ sender, brandId }` so receivers know which brand changed and the sender can ignore its own event. */ +function emitBrandSignal(req, brandId = null) { + const companyId = req.user?.selectedCompanyId; + if (companyId == null) return; + const clientId = req.headers?.['x-client-id']; + emit('brand', { companyId, data: { sender: typeof clientId === 'string' ? clientId : '', brandId } }); +} + /** * Registers brand routes for the authenticated user's selected company. * @param {import('express').Application} app @@ -75,14 +85,44 @@ export function register(app, basePath = '') { * application/json: * schema: * $ref: '#/components/schemas/Error' - * put: + */ + app.get(`${basePath}/brand`, async (req, res) => { + const selected = requireSelectedCompany(req); + if (selected.error) { + return res.status(400).json({ error: selected.error }); + } + + try { + const brands = await db.list(req.user.id); + res.status(200).json({ brands }); + } catch (error) { + const status = brandErrorStatus(error); + if (status === 500) console.error('List brands failed:', error); + res.status(status).json({ error: error.message }); + } + }); + + /** + * @openapi + * /brand: + * post: * tags: [Brand] - * summary: Upsert brand + * summary: Create brand * description: | - * Insert or update brand by `name` for the selected company. `gsprpinfo` optional. + * Creates a brand for the selected company. Fails with `Brand already exists` + * if the name is taken. Emits SSE signal `brand` with payload + * `{ sender, brandId }` (`sender` echoes the caller's `x-client-id`). * security: * - cookieAuth: [] - * x-markdown: brand/upsert.md + * x-signals: [brand] + * x-markdown: brand/create.md + * parameters: + * - in: header + * name: x-client-id + * required: false + * schema: + * type: string + * description: Sender id echoed as SSE payload so the sender can ignore its own event. * requestBody: * required: true * content: @@ -99,8 +139,8 @@ export function register(app, basePath = '') { * name: Acme * gsprpinfo: 'Acme GmbH' * responses: - * 200: - * description: Upserted brand + * 201: + * description: Created brand * content: * application/json: * schema: @@ -132,24 +172,14 @@ export function register(app, basePath = '') { * application/json: * schema: * $ref: '#/components/schemas/Error' + * 409: + * description: Brand already exists + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' */ - app.get(`${basePath}/brand`, async (req, res) => { - const selected = requireSelectedCompany(req); - if (selected.error) { - return res.status(400).json({ error: selected.error }); - } - - try { - const brands = await db.list(req.user.id); - res.status(200).json({ brands }); - } catch (error) { - const status = brandErrorStatus(error); - if (status === 500) console.error('List brands failed:', error); - res.status(status).json({ error: error.message }); - } - }); - - app.put(`${basePath}/brand`, async (req, res) => { + app.post(`${basePath}/brand`, async (req, res) => { const selected = requireSelectedCompany(req); if (selected.error) { return res.status(400).json({ error: selected.error }); @@ -158,14 +188,137 @@ export function register(app, basePath = '') { const { name, gsprpinfo } = req.body ?? {}; try { - const brand = await db.upsert(req.user.id, { + const brand = await db.create(req.user.id, { name, gsprpinfo, }); + emitBrandSignal(req, brand.id); + res.status(201).json({ brand }); + } catch (error) { + const status = brandErrorStatus(error); + if (status === 500) console.error('Create brand failed:', error); + res.status(status).json({ error: error.message }); + } + }); + + /** + * @openapi + * /brand/{id}: + * patch: + * tags: [Brand] + * summary: Rename brand + * description: | + * Renames/updates a brand by id for the selected company. Fails with + * `Brand already exists` if the name is taken by another brand. + * When the name changes, linked articles (`brand_id`) get their + * `updated_at` bumped; the response reports `touchedArticleCount`. + * Emits SSE signal `brand` with payload `{ sender, brandId }` + * (`sender` echoes the caller's `x-client-id`). + * security: + * - cookieAuth: [] + * x-signals: [brand] + * x-markdown: brand/update.md + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: integer + * - in: header + * name: x-client-id + * required: false + * schema: + * type: string + * description: Sender id echoed as SSE payload so the sender can ignore its own event. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * gsprpinfo: + * type: string + * example: + * name: Acme GmbH + * responses: + * 200: + * description: Updated brand + * content: + * application/json: + * schema: + * type: object + * properties: + * brand: + * type: object + * required: [id, name, touchedArticleCount] + * properties: + * id: + * type: integer + * name: + * type: string + * gsprpinfo: + * type: string + * nullable: true + * touchedArticleCount: + * type: integer + * description: Number of linked articles whose updated_at was bumped. + * 400: + * description: Invalid brand, invalid id, or no company selected + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + * 401: + * description: Unauthorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + * 403: + * description: Not authorized + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + * 404: + * description: Brand or company not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + * 409: + * description: Brand already exists + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/Error' + */ + app.patch(`${basePath}/brand/:id`, async (req, res) => { + const selected = requireSelectedCompany(req); + if (selected.error) { + return res.status(400).json({ error: selected.error }); + } + + const id = Number(req.params.id); + if (!Number.isInteger(id) || id <= 0) { + return res.status(400).json({ error: 'id is required' }); + } + + const { name, gsprpinfo } = req.body ?? {}; + + try { + const brand = await db.update(req.user.id, id, { + name, + gsprpinfo, + }); + emitBrandSignal(req, id); res.status(200).json({ brand }); } catch (error) { const status = brandErrorStatus(error); - if (status === 500) console.error('Upsert brand failed:', error); + if (status === 500) console.error('Update brand failed:', error); res.status(status).json({ error: error.message }); } }); @@ -178,9 +331,11 @@ export function register(app, basePath = '') { * summary: Delete brand * description: | * Deletes the brand for the selected company. Fails with `Brand in use` - * if any article still references it. + * if any article still references it. Emits SSE signal `brand` + * with payload `{ sender, brandId }`. * security: * - cookieAuth: [] + * x-signals: [brand] * x-markdown: brand/delete.md * parameters: * - in: path @@ -235,6 +390,7 @@ export function register(app, basePath = '') { try { await db.remove(req.user.id, id); + emitBrandSignal(req, id); res.sendStatus(204); } catch (error) { const status = brandErrorStatus(error); @@ -251,9 +407,11 @@ export function register(app, basePath = '') { * summary: Add or replace brand image * description: | * Uploads an image for the brand (AVIF, WebP, or octet-stream). Converted to AVIF full and thumbnail. - * Replaces any existing image for this brand. + * Replaces any existing image for this brand. Emits SSE signal `brand` + * with payload `{ sender, brandId }`. * security: * - cookieAuth: [] + * x-signals: [brand] * parameters: * - in: path * name: id @@ -328,9 +486,10 @@ export function register(app, basePath = '') { * delete: * tags: [Brand] * summary: Delete brand image - * description: Removes the brand's image. Sets thumbW, thumbH, thumbHash back to null. + * description: Removes the brand's image. Sets thumbW, thumbH, thumbHash back to null. Emits SSE signal `brand` with payload `{ sender, brandId }`. * security: * - cookieAuth: [] + * x-signals: [brand] * parameters: * - in: path * name: id @@ -410,6 +569,7 @@ export function register(app, basePath = '') { try { const brand = await db.addImage(req.user.id, id, imageBuffer); + emitBrandSignal(req, id); res.status(201).json({ brand }); } catch (error) { const status = brandErrorStatus(error); @@ -432,6 +592,7 @@ export function register(app, basePath = '') { try { const brand = await db.deleteImage(req.user.id, id); + emitBrandSignal(req, id); res.status(200).json({ brand }); } catch (error) { const status = brandErrorStatus(error); diff --git a/src/api/routes/events.js b/src/api/routes/events.js index 8f0d1dc..d4aa8de 100644 --- a/src/api/routes/events.js +++ b/src/api/routes/events.js @@ -38,6 +38,12 @@ export function register(app, basePath = '') { * | `category` | `POST /category`, `PATCH /category/{id}`, `PUT /category/order`, `DELETE /category/{id}`, `POST /category/{id}/image`, `DELETE /category/{id}/image` | * | `articles` | `POST /article`, `PATCH /article/{sku}`, `DELETE /article/{sku}` | * | `article-` | `PUT /article/{sku}/weight`, `PUT /article/{sku}/prices`, `PUT /article/{sku}/eans`, `PUT /article/{sku}/categories`, `PUT /article/{sku}/brand`, `PUT /article/{sku}/images/order`, `POST /article/{sku}/image`, `DELETE /article/{sku}/image/{fullHash}` | + * | `brand` | `POST /brand`, `PATCH /brand/{id}`, `DELETE /brand/{id}`, `POST /brand/{id}/image`, `DELETE /brand/{id}/image` | + * + * The `brand` signal carries a JSON payload `{ sender, brandId }`: + * `sender` echoes the caller's `x-client-id` header (empty string if + * absent) so the sender can ignore its own event; `brandId` is the + * affected brand id (`null` only if unknown). Other signals send `{}`. * * Signals are only sent when the mutation actually succeeds (2xx). * Failed or no-op validation errors do not emit. The `article-` @@ -49,7 +55,8 @@ export function register(app, basePath = '') { * 200: * description: | * SSE stream (`text/event-stream`). Each signal is an SSE event - * whose name is the signal id and whose data is `{}`. + * whose name is the signal id. Most signals send `{}` as data; + * `brand` sends `{ "sender": "", "brandId": 12 }`. * content: * text/event-stream: * schema: @@ -63,6 +70,9 @@ export function register(app, basePath = '') { * * event: article-42000045 * data: {} + * + * event: brand + * data: {"sender":"tab-1","brandId":12} * 400: * description: No company selected * content: diff --git a/src/api/sse.js b/src/api/sse.js index a897ef0..3369884 100644 --- a/src/api/sse.js +++ b/src/api/sse.js @@ -57,12 +57,13 @@ export function closeAll() { /** * Broadcast a named signal to subscribers of the given company. * @param {string} signal - SSE event name (e.g. `category`) - * @param {{ companyId: number }} options + * @param {{ companyId: number, data?: string|object }} options */ -export function emit(signal, { companyId }) { +export function emit(signal, { companyId, data }) { if (companyId == null) return; - const frame = `event: ${signal}\ndata: {}\n\n`; + const payload = data == null ? '{}' : (typeof data === 'string' ? data : JSON.stringify(data)); + const frame = `event: ${signal}\ndata: ${payload}\n\n`; for (const client of clients) { if (client.companyId !== companyId) continue; if (client.res.writableEnded) continue; diff --git a/src/db/brand.js b/src/db/brand.js index 59be0f5..47eb24d 100644 --- a/src/db/brand.js +++ b/src/db/brand.js @@ -5,6 +5,16 @@ export async function list(googleAuthId) { return store.brand.list(googleAuthId); } +export async function create(googleAuthId, data) { + const store = await getStore(); + return store.brand.create(googleAuthId, data); +} + +export async function update(googleAuthId, id, data) { + const store = await getStore(); + return store.brand.update(googleAuthId, id, data); +} + export async function upsert(googleAuthId, data) { const store = await getStore(); return store.brand.upsert(googleAuthId, data);